Web 2 Py

Descargar como docx, pdf o txt
Descargar como docx, pdf o txt
Está en la página 1de 510

Creo que la habilidad para crear fcilmente aplicaciones web de alta calidad es de una

importancia crtica para el desarrollo de una sociedad abierta y libre. Esto evita que los
jugadores ms pesados puedan monopolizar la libre circulacin de la informacin.
Por esa razn, comenc el proyecto web2py en 2007, inicialmente como herramienta de
aprendizaje con el fin de facilitar el desarrollo web, para que fuera ms rpido y ms seguro.
Con el tiempo, se ha ganado el afecto de miles de usuarios idneos, y de cientos de
desarrolladores. Nuestro esfuerzo colectivo ha creado uno de los marcos de desarrollo de
cdigo abierto ms completo para el desarrollo web empresarial.
Como resultado, en 2011, web2py gan el Premio Bossie para el Mejor Sofware de
Desarrollo de Cdigo Abierto, y en el 2012 gan el Premio Anual a las Mejores Tecnologas de
InfoWorld.
Como vers a lo largo de los captulos que siguen, web2py intenta bajar la barrera de ingreso
al desarrollo web centrndose en tres metas principales:
Fcil uso. Esto implica reducir el tiempo empleado en aprender y desplegar as como tambin
los costos de desarrollo y mantenimiento. Es por esto que web2py es un marco
completamente integrado y sin dependencias. No requiere de instalacin y no tiene archivos
de configuracin. Todo funciona instantneamente, incluyendo un servidor web, la base de
datos y un entorno de desarrollo para navegador que provee de acceso a las caractersticas
principales. La API incluye solamente 12 objetos del ncleo, que son fciles de usar y
memorizar. Puede interactual con la mayor parte de los servidores web y bases de datos y con
todas libreras de Python.
Desarrollo gil. Cada funcin de web2py tiene un comportamiento por defecto (que se puede
sobreescribir). Por ejemplo, ni bien hayas especificado tus modelos de datos, tendrs acceso
a la interfaz administrativa de la base de datos para navegador. Adems, web2py genera
automticamente los formularios para tus datos y te permite exponer fcilmente los datos en
HTML, XML, JSON, RSS, etc. web2py provee de algunos widget de alto nivel como la wiki y el
grid para crear aplicaciones avanzadas rpidamente.
Seguridad. La Capa de Abstraccin de la Base de Datos (DAL) elimina las inyecciones de
SQL. El lenguaje de plantillas previene las vulnerabilidades de secuencia de comandos en
sitios cruzados o (Cross Site Scripting). Los formularios generados por web2py proveen de
validacin de campos y bloquean las falsificaciones de solicitud en sitios cruzados (CSRF).
Las contraseas siempre se almacenan codificadas. Las sesiones se almacenan del lado del
servidor para prevenir la manipulacin de los cookie (Cookie Tampering). Los cookie se
identifican con UUID para evitar el secuestro de sesiones o Session Hijacking.

web2py fue construido pensando en la perspectiva del usuario y es constantemente

optimizado internamente para que sea ms rpido y ligero, pero siempre conservando
la compatibilidad hacia atrs.
Puedes usar web2py en forma gratuita. Si te beneficias usndolo, espero que eso te incentive
a agradecerlo contribuyendo a la sociedad en la forma que tu elijas.

Introduccin
web2py[web2py] es un marco de cdigo abierto para el desarrollo gil de aplicaciones web seguras
conectadas a servicios de bases de datos; est programado en Python [python] y es programable
en Python. web2py es un marco de desarrollo completamente integrado, es decir, contiene
todos los componentes que necesitas para armar aplicaciones web totalmente funcionales.
web2py se ha diseado para guiar al desarrollador web para que siga buenas prcticas en
ingeniera de software, como por ejemplo el uso del patrn Modelo Vista Controlador (MVC).
web2py separa la representacin de los datos (el modelo) de la presentacin de los datos (la
vista) y de los algoritmos y flujo de operacin (el controlador). web2py provee de libreras que
ayudan al desarrollador en el diseo, implementacin y realizacin de pruebas, y las
administra de forma que las distintas libreras trabajen en conjunto.
web2py se ha construido tomando como base la seguridad. Esto significa que
automticamente resuelve muchos de los problemas que pueden generar vulnerabilidades de
seguridad, siguiendo prcticas recomendadas por fuentes autorizadas. Por ejemplo, web2py
valida todo dato ingresado (para prevenir la inyeccin de cdigo fuente), escapa toda salida
(para prevenir las Secuencias de comandos en sitios cruzados o cross-site scripting), cambia
los nombres de archivos subidos (para evitar el ataque de tipo directory traversal o
ataquepunto punto barra). web2py se encarga de los problemas de seguridad ms
importantes, para que los desarrolladores tengan pocas oportunidades de introducir
vulnerabilidades.
web2py incluye una Capa de Abstraccin de la Base de Datos (DAL) que escribe SQL [sql-w] en
forma dinmica para que tu, el desarrollador, no tengas que hacerlo. La DAL sabe cmo
generar el SQL en forma transparente para SQLite [sqlite], MySQL[mysql], PostgreSQL[postgres],
MSSQL[mssql], FireBird[firebird], Oracle[oracle], IBM DB2[db2], Informix[informix] e Ingres[ingresdb].
La DAL adems puede generar llamadas a funciones para el Google Datastore cuando se
corre sobre el Google App Engine (GAE) [gae]. Experimentalmente, se soportan ms bases de
datos y se agregan nuevas constantemente. Si necesitas un adaptador para otra base de
datos no listada, puedes buscar informacin sobre nuevos adaptadores en el sitio de web2py o
en la lista de correo. Una vez que una o ms tablas de la base de datos se han definido,
web2py genera en forma automtica una interfaz administrativa para la base de datos
completamente funcional con la que se pueden consultar los datos almacenados.

web2py difiere de otros marcos de desarrollo en que es el nico marco que adopta el
paradigma Web 2.0 en forma total, segn el cual la web es la computadora. De hecho, web2py
no requiere configuracin o instalacin alguna; puede correr en cualquier arquitectura que
incluya Python (Windows, Windows CE, Mac OS X, iOS, y Unix/Linux), y las fases de
desarrollo, despliegue y mantenimiento de la aplicacin se pueden realizar por medio de una
interfaz web en forma remota o local. web2py corre con CPython (la implementacin en C) y
PyPy (Python escrito en Python), para las versiones de Python 2.5, 2.6 y 2.7.
web2py provee de un sistema de ticket para reporte de errores. Si ocurre un error, se enva un
ticket al usuario, y se registra el error en un log para el administrador.
web2py es cdigo abierto liberado bajo los trminos de la licencia LGPL versin 3.
Otra caracterstica importante de web2py es que nosotros, sus desarrolladores, nos hemos
propuesto mantener la compatibilidad hacia atrs en versiones futuras. As lo hemos hecho
desde la primera versin liberada de web2py en octubre del 2007. Se han agregado nuevas
caractersticas y reparacin de fallas, pero si un programa funcionaba con web2py 1.0, ese
programa tambin funcionar del mismo modo actualmente, o incluso mejor.
Estos son algunos ejemplos de instrucciones de web2py que ilustran su potencia y
simplicidad. El siguiente cdigo:
db.define_table('persona', Field('nombre'), Field('imagen', 'upload'))

crea una tabla de la base de datos llamada "persona" que tiene dos campos: "nombre", una
cadena; e "imagen", algo que debe ser subido (la imagen en s). Si la tabla ya existe pero no
coincide con la definicin, se alterar segn sea necesario.
Dada la tabla definida ms arriba, el siguiente cdigo:
formulario = SQLFORM(db.persona).process()

crea un formulario de ingreso para esa tabla que permite a los usuarios subir imgenes.
Adems realiza la validacin de los formularios enviados, cambia el nombre de la imagen
subida en forma segura, almacena la imagen en un archivo, inserta el registro correspondiente
en la base de datos, previene el envo duplicado del formulario, y eventualmente modifica el
formulario mismo agregando mensajes de error si los datos enviados por el usuario no
pasaron la validacin.
Este cdigo embebe una wiki completa y lista para usar con etiquetas, la correspondiente
nube de etiquetas, incrustar distintos tipos de recursos y soporte para oembed:
def index(): return auth.wiki()

Por otra parte, el siguiente cdigo:


@auth.requires_permission('read', 'persona')
def f(): ....

evita los visitantes accedan a la funcin f , a menos que el visitante sea miembro del grupo
cuyos miembros tienen permiso para leer ("read") registros de la tabla "persona". Si el visitante
no se ha autenticado, se lo redirige a la pgina de acceso (provista por defecto por web2py).
Adems web2py tiene soporte para componentes, es decir, acciones que se pueden
incorporar en una vista para que interacten con el visitante a travs de Ajax sin tener que
refrescar toda la pgina. Esto se hace por medio de un ayudante LOAD que permite un diseo
de aplicaciones verdaderamente modular; esto se trata en el captulo 3 en relacin con wiki y
con cierto detalle en el ltimo captulo del libro.
Esta 5. edicin del libro cubre web2py 2.4.1 y versiones posteriores.

Principios
La programacin en Python sigue tpicamente estos principios bsicos:
o

No te repitas (DRY).

Debera haber una nica forma para hacer las cosas.

Explcito es mejor que implcito.

web2py respeta estrictamente los dos primeros principios obligando al desarrollador a que use
prcticas reconocidas de ingeniera de software que desalientan la repeticin de cdigo
fuente. web2py gua al desarrollador a travs de casi todas las tareas comunes en el
desarrollo de aplicaciones web (creacin y procesamiento de formularios, administracin de
sesiones, cookie, errores, etc.).
web2py se diferencia de otros marcos de desarrollo en relacin con el tercer principio, que a
veces entra en conflicto con los otros dos. En especial, web2py no importa las aplicaciones de
usuarios, sino que las ejecuta en un contexto predefinido. Este contexto expone las palabras
clave de Python (Python keywords) as como tambin las palabras clave de web2py.
Para algunos esto puede parecer magia, pero no debera ser as. Simplemente, en realidad,
algunos mdulos ya se han importado sin que tu debas hacerlo. web2py intenta eludir la
molesta caracterstica de otros marcos de desarrollo que obligan al desarrollador a que
importe los mismos mdulos al inicio de cada modelo y de cada controlador.
Al importar sus propios mdulos, web2py ahorra tiempo y evita errores y confusiones,
siguiendo de esa forma el espritu de no repetirse y tambin de la unicidad del mtodo para
una tarea especfica.
Si el desarrollador quiere usar otros mdulos de Python o mdulos de terceros, esos mdulos
se deben importar en forma explcita, como con cualquier otro programa en Python.

Marcos de Desarrollo Web

En su nivel ms elemental, una aplicacin web consiste de un conjunto de programas (o


funciones) que se ejecutan cuando se visita un determinado URL. La salida del programa se
devuelve al visitante y es procesada por el navegador.
El propsito de los marcos de desarrollo web es el de permitir a los desarrolladores la creacin
de nuevas aplicaciones con rapidez, facilidad y sin cometer errores. Esto se logra presentando
interfaces API y herramientas que reducen y simplifican la cantidad de cdigo que se requiere.
Los dos mtodos clsicos para el desarrollo de aplicaciones web son:
o

Generar HTML[html-w] [html-o] al vuelo, en forma programtica.

Embeber cdigo fuente en pginas HTML.

La primera tcnica es la que se ha utilizado, por ejemplo, en los primeros script CGI. El
segundo modelo de trabajo es el que se usa para PHP [php] (donde el cdigo est escrito en
PHP, un lenguaje parecido a C), ASP (con cdigo Visual Basic), y JSP (que utiliza Java).
Aqu se puede ver un ejemplo de programa en PHP que, cuando se ejecuta, recupera
informacin de una base de datos y devuelve una pgina HTML que muestra los registros
recuperados:
<html><body><h1>Registros</h1><?
mysql_connect(localhost, usuario, clave);
@mysql_select_db(database) or die( "Imposible recuperar datos");
$consulta="SELECT * FROM contactos";
$resultado=mysql_query($consulta);
mysql_close();
$i=0;
while ($i < mysql_numrows($resultado)) {
$nombre=mysql_result($resultado, $i, "nombre");
$telefono=mysql_result($resultado, $i, "telefono");
echo "<b>$nombre</b><br>Telfono:$telefono<br /><br /><hr /><br />";
$i++;
}
?></body></html>

El problema con este tipo de solucin es que el cdigo est incrustado en el HTML, pero ese
mismo cdigo tambin debe generar HTML adicional y adems generar las instrucciones SQL
para consultar la base de datos, enredando mltiples capas de la aplicacin y haciendo que el

cdigo sea de difcil lectura y mantenimiento. La situacin es an peor para el caso de


aplicaciones que usan Ajax, y la complejidad crece con el nmero de pginas (archivos) que
componen la aplicacin.
La funcionalidad implementada por el cdigo arriba se puede expresar en web2py con dos
lneas de cdigo en Python:
def index():
return HTML(BODY(H1('Registros'), db().select(db.contactos.ALL)))

En este simple ejemplo, la estructura de la pgina HTML es representada en forma


programtica por los objetos HTML , BODY , Y H1 ; la base de datos db se consulta con el
comando select ; por ltimo, todo se serializa como HTML. Observa que db no es una
palabra especial sino una variable definida por el usuario. Usaremos este nombre como una
convencin para referirnos en general a una conexin de la base de datos.
Los marcos de desarrollo web se suelen separar en dos categoras: Un marco de
desarrollo glued (pegado) se arma ensamblando (pegando juntos) muchos componentes de
terceros. Un marco de desarrollo full-stack (completo e integrado) se arma creando
componentes especialmente diseados para estar fuertemente ligados y trabajar en conjunto.
web2py es un marco de desarrollo full-stack. Prcticamente todos sus elementos se han
creado desde cero y se disearon para funcionar en conjunto, pero adems estn preparados
para funcionar en forma independientemente, como componentes de terceros de otras
aplicaciones. Por ejemplo, la Capa de Abstraccin de la Base de Datos (DAL) o el lenguaje de
plantillas
se
pueden
usar
en
forma
independiente
de
web2py,
importando gluon.dal o gluon.template en tus propias aplicaciones. gluon es el nombre del
paquete que contiene las libreras del sistema. Algunas libreras de web2py, como las que
crean y procesan formularios a partir de tablas de bases de datos, tienen como dependencias
otras partes de web2py. Adems, web2py puede funcionar con libreras Python de terceros,
incluyendo otros lenguajes de plantillas y capas de abstraccin de bases de datos, pero estas
libreras no se integrarn en el mismo grado que los componentes originales.

Modelo-Vista-Controlador
web2py alienta al desarrollador a que separe la representacin de los datos (el modelo), la
presentacin de los datos (la vista) y el flujo de operacin o workflow de la aplicacin (el del
controlador). Consideremos nuevamente el ejemplo anterior y veamos cmo podemos crear
una aplicacin en torno de l. Este es un ejemplo de la interfaz de edicin MVC:

El tpico flujo de trabajo de una solicitud en web2py se describe en el siguiente


diagrama:

En el diagrama:
o

El servidor puede ser el o bien el servidor incorporado o un servidor de terceros, como


por ejemplo Apache. El servidor maneja mltiples hilos (tiene soporte para multithreading).

"main" es la aplicacin principal WSGI. Realiza todas las tareas comunes y envuelve la
aplicacin del usuario. Se encarga del manejo de las cookie, sesiones, transacciones,
enrutamiento de URL y enrutamiento inverso, y la administracin de direcciones
odispatching. Puede administrar y realizar transmisiones de archivos estticos si el
servidor web no se ha configurado para hacerlo.

Los componentes de los Modelos, las Vistas y los Controladores, conforman la


aplicacin del usuario.

o
o

Se pueden alojar mltiples aplicaciones en la misma instancia de web2py.


Las flechas suaves representan la comunicacin con el motor de la base de datos (o
los motores). Las consultas a la base de datos se pueden escribir en SQL puro (no est
recomendado) o usando la Capa de Abstraccin de la Base de Datos (la forma

recomendada), de manera que el cdigo de las aplicaciones no depende de una base de


datos especfica.
o

El administrador de direcciones asocia la URL solicitada a una llamada a funcin en el


controlador. La salida de la funcin puede ser una cadena o un diccionario conteniendo
pares nombre-valor (una hash table). Los datos contenidos en el diccionario se
convierten en la pgina HTML. Si el visitante solicita la misma pgina en XML, web2py
busca una vista que pueda convertir el diccionario a XML. El desarrollador puede crear
vistas que conviertan pginas en cualquier protocolo soportado (HTML, XML, JSON,
RSS, CSV y RTF) o en protocolos personalizados adicionales.

Todas las llamadas se envuelven en una transaccin, y toda excepcin no manejada


hace que la transaccin recupere el estado inicial. Si la solicitud tiene xito, se aplican los
cambios a la base de datos.

web2py adems maneja sesiones y los cookie de sesin en forma automtica, y


cuando se aplican los cambios de una transaccin, tambin se almacena la sesin, a
menos que se especifique lo contrario.

Es posible registrar tareas recurrentes (por medio de cron) para que se ejecuten segn
un programa en un momento determinado y/o al finalizar ciertas acciones. De esta forma
es posible correr tareas largas y que requieren un uso intensivo del hardware en segundo
plano sin que la navegacin se torne ms lenta.

He aqu una aplicacin mnima pero completa MVC, que consiste en tres archivos:
"db.py" es el modelo:
db = DAL('sqlite://storage.sqlite')
db.define_table('contacto',
Field('nombre'),
Field('telefono'))

Este cdigo conecta a la base de datos (para este ejemplo una base de datos SQLite
almacenada en el archivo storage.sqlite ) y define una tabla llamada contacto . Si la tabla no
existe, web2py la crea y, en forma transparente, genera el cdigo SQL en el dialecto apropiado
para el motor de la base de datos especfico que usa.
El desarrollador puede ver el SQL generado, pero no necesita modificarlo cuando la base de
datos, que por defecto es SQLite, se reemplaza por MySQL, PostgreSQL, MSSQL, FireBird,
Oracle, DB2, Informix, Interbase, Ingres, o Google App Engine (tanto para SQL como para
NoSQL).

Una vez que se ha definido y creado una tabla, web2py adems genera una interfaz de
administracin de la base de datos completamente funcional, llamada appadmin, para poder
acceder a esa base de datos y a sus tablas.
"default.py" es el controlador:
def contactos():
grid=SQLFORM.grid(db.contacto, user_signature=False)
return locals()

En web2py, los URL se asocian a mdulos de Python y llamadas a funciones. En este caso, el
controlador contiene una nica funcin o "accin" llamada contactos . Toda accin puede
devolver una cadena (la pgina web devuelta) o un diccionario de Python (un conjunto de
pares clave:valor ) o un conjunto de las variables locales (como en este ejemplo). Si la funcin
devuelve un diccionario, se pasa a la vista con el mismo nombre que el controlador y la
funcin, que a su vez convierte (render) la pgina. En este ejemplo, la
funcin contactos genera
una
grilla
que
incluye
los
comandos
recuperar/buscar/crear/modificar/borrar para la tabla db.contacto y devuelve la grilla a la vista.
"default/contactos.html" es la vista:
{{extend 'layout.html'}}
<h1>Administracin de contactos</h1>
{{=grid}}

Esta vista es llamada automticamente por web2py luego de que la funcin del controlador
asociado (accin) se ejecute. La tarea de esta vista es la conversin de las variables en el
diccionario devuelto (para este caso grid ) en HTML. El archivo de la vista est escrito en
HTML, pero embebe cdigo de Python delimitado por los signos especiales {{ y }} . Esto es
bastante distinto al ejemplo de cdigo fuente de PHP, porque el nico cdigo embebido en el
HTML es cdigo de la "capa de presentacin". El archivo "layout.html" al que se hace
referencia en la parte superior de la vista es provisto por web2py constituye el diseo de
pgina bsico para toda aplicacin de web2py. El archivo del diseo de pgina se puede
reemplazar o modificar fcilmente.

Por qu web2py?
web2py es uno entre los varios marcos de aplicaciones web, pero tiene algunas caractersticas
atractivas que lo diferencian.
web2py se desarroll inicialmente como una herramienta para enseanza, con las siguientes
motivaciones bsicas:

Que fuera fcil para los usuarios aprender desarrollo web del lado del servidor sin
comprometer la funcionalidad. Por esa razn, web2py no requiere instalacin ni
configuracin, no tiene dependencias (excepto la distribucin de cdigo fuente, que
requiere Python 2.5 y los mdulos de las libreras estndar), y expone la mayor parte de
su funcionalidad a travs de una interfaz de navegador web, incluyendo un entorno
integrado de desarrollo con un depurador y una interfaz para la base de datos.

web2py se ha mantenido estable desde el primer da porque sigue un diseo topdown ; por ejemplo, su API fue diseada antes de ser implementada. Incluso si bien

nuevas funcionalidades fueron agregadas, web2py nunca ha introducido


incompatibilidades hacia atrs, y no va a introducir incompatibilidades cuando se
agreguen nuevas caractersticas en el futuro.
o

web2py resuelve activamente los problemas de seguridad ms importantes que plagan


a muchas aplicaciones web modernas, segn el anlisis de OWASP [owasp] que se puede
consultar ms abajo.

web2py es liviano. Su ncleo de libreras, incluyendo la Capa de Abstraccin de la


Base de Datos, el lenguaje de plantillas, y el conjunto completo de ayudantes pesan
1.4MB. El cdigo fuente completo incluyendo aplicaciones de ejemplo e imgenes pesa
10.4MB.

web2py tiene una huella (footprint) pequea y es realmente rpido. Utiliza el servidor
WSGI Rocket [rocket] desarrollado por Timothy Farrell. Es tan rpido como Apache con
mod_wsgi, y adems tiene soporte para SSL e IPv6.

web2py usa sintaxis Python para modelos, controladores y vistas, pero no importa
modelos y controladores (a diferencia del resto de los marcos de desarrollo) - sino que
los ejecuta. Esto significa que las app pueden instalarse, desinstalarse y modificarse sin
tener que reiniciar el servidor web (incluso en produccin), y aplicaciones diferentes
pueden coexistir sin que sus mdulos interfieran entre s.

web2py utiliza una Capa de Abstraccin de la Base de Datos (DAL) en lugar


del mapeo objeto-relacional (ORM). Desde un punto de vista conceptual, esto significa
que distintas tablas de bases de datos se asocian ( map ) en diferentes instancias de una
clase Table y no en distintas clases, mientras que los registros se asocian o mapean a
instancias de una clase Row , no a instancias de la clase de la tabla correspondiente.
Desde un punto de vista prctico, significa que la sintaxis SQL se asocia en una relacin
uno a uno en la sintaxis de DAL, y no es necesaria una "caja negra" con programacin
compleja de metaclases como es comn en los ORM ms populares, que implicara un
incremento de la latencia.

WSGI [wsgi-w,wsgi-o] (Web Server Gateway Interface) es un estndar moderno de Python para
comunicaciones entre un servidor web y aplicaciones Python.
Aqu se puede ver una captura de la interfaz administrativa de web2py admin:

Seguridad
El Open Web Application Security Project[owasp] (OWASP) es una libre y abierta dedicada a
mejorar la seguridad para aplicaciones de software.
OWASP ha listado los mayores problemas de seguridad que pueden poner en peligro a las
aplicaciones web. Esa lista se reproduce a continuacin, junto con una descripcin de cmo
se resuelve cada uno de los problemas en web2py:
o

Cross Site Scripting (XSS): las vulnerabilidades de Secuencias de comandos en sitios


cruzados ocurren cuando una aplicacin toma datos provistos por el usuario y los enva a
un navegador web sin previamente validar o codificar el contenido. XSS permite a los
atacantes ejecutar script en el navegador de la vctima que pueden tomar control de la
sesin del usuario, desfigurar sitios web, probablemente introducir "gusanos",

etc. web2py, por defecto, "escapa" todas las variables procesadas en la vista, previniendo
el XSS.
o

Injection Flaws (vulnerabilidades de inyeccin de cdigo fuente): La inyeccin, en


particular la de SQL, es comn en las aplicaciones web. Las inyecciones ocurren cuando
informacin provista por el usuario es enviada como parte de una instruccin o consulta.
La informacin hostil del atacante engaa al intrprete para que ejecute comandos no
esperados o para que modifique informacin.web2py incluye una Capa de Abstraccin de
la Base de Datos que hace imposible la inyeccin de SQL. Normalmente, las
instrucciones SQL no son escritas por el desarrollador. En cambio, el SQL es generado
dinmicamente por el DAL, asegurando que toda informacin ingresada se haya
escapado correctamente.

Malicious File Execution (ejecucin maliciosa de archivos): El cdigo vulnerable a la


inclusin remota de archivos (RFI) permite a los atacantes incluir cdigo hostil e
informacin, dando como resultado ataques devastadores, como por ejemplo el
compromiso total del servidor. web2py permite nicamente la ejecucin de funciones
expuestas, previniendo la ejecucin maliciosa de archivos. Las funciones importadas
nunca se exponen; slo se exponen las acciones. web2py utiliza una interfaz
administrativa web que hace muy fcil el seguimiento de las acciones que se han
expuesto.

Insecure Direct Object Reference (referencias directas a objetos inseguros): una


referencia directa a un objeto ocurre cuando un desarrollador expone una referencia a un
objeto de implementacin interna, como un archivo, directorio, registro de la base de
datos o clave, como URL o parmetro de formulario. Los atacantes pueden manipular
esas referencias para acceder a otros objetos sin autorizacin. web2py no expone ningn
objeto interno; es ms, web2py valida toda URL, previniendo de esa forma los
denominados ataques de tipo directory traversal . web2py tambin provee un mecanismo
simple para la creacin de formularios que automticamente valida todas los datos
ingresados.

Cross Site Request Forgery (CSRF) o Suplantacin de identidad entre servidores: Un


ataque CSRF fuerza al navegador de una vctima autenticada a que enve una solicitud
preautenticada a una aplicacin web vulnerable, que entonces fuerza al navegador de la
vctima a realizar una accin hostil en beneficio del atacante. El CSFR puede ser tan
grave como la importancia de la aplicacin web a la que ataca. web2py previene el CSRF
tanto como los envos de formularios duplicadas por error asignando una clave nica
aleatoria a cada formulario. Incluso, web2py utiliza UUID para la cookie de la sesin.

Information Leakage and Improper Error Handling (Filtrado de informacin y manejo


inapropiado de errores): Las aplicaciones pueden filtrar inadvertidamente informacin
sobre su configuracin, constitucin interna, o violar la privacidad a travs de una serie

de problemas en la aplicacin misma. Los atacantes utilizan estas debilidades para


hurtar informacin sensible, o perpetrar ataques ms peligrosos. web2py incluye un
sistema de ticket de reporte. Ningn error puede resultar en cdigo expuesto al usuario.
Todos los errores se almacenan y se enva el ticket al usuario para permitir su
seguimiento. Pero el detalle de los errores slo es accesible para el administrador.
o

Broken Authentication and Session Management (Fallos de autenticacin y manejo de


sesiones): Las credenciales y claves de sesin a menudo no se protegen
apropiadamente. Los atacantes pueden acceder a contraseas, claves o referencias
cifradas para falsificar la identidad del usuario (por ejemplo autenticndose como un
usuario registrado). web2py incorpora un mecanismo para autenticacin administrativa, y
maneja las sesiones en forma independiente para cada aplicacin. La interfaz
administrativa adems fuerza el uso de cookie seguras cuando el cliente no est en
"localhost". Para las aplicaciones, incluye una poderosa API para Control de Acceso
Basada en Roles ( RBAC )

Insecure Cryptographic Storage (Almacenamiento criptogrfico inseguro): Las


aplicaciones web raramente utilizan funciones criptogrficas apropiadamente para
proteger datos y credenciales. Los atacantes utilizan la informacin dbilmente protegida
para el robo de identidad y otros crmenes, como el fraude con tarjetas de
crdito. web2py usa los algoritmos MD5 o el HMAC+SHA-512 para proteger las
contraseas almacenadas. Existen otros algoritmos tambin disponibles.

Insecure Communications (inseguridad de transferencias): Las aplicaciones


frecuentemente omiten la encripcin del trfico de red cuando es necesario para proteger
comunicaciones sensibles. web2py incluye el [ssl] servidor Rocket WSGI con soporte para
SSL, pero tambin puede utilizar Apache o Lighthttpd y mod_ssl para servir
comunicaciones con cifrado SSL.

Failure to Restrict URL Access (Acceso irrestricto con URL): Con frecuencia una
aplicacin slo protege las funcionalidades sensibles evitando mostrar link o direcciones
URL a usuarios no autorizados. Los atacantes pueden utilizar esta debilidad para
acceder y realizar operaciones no autorizadas accediendo a esos URL
directamente. web2py asocia las solicitudes URL a mdulos y funciones de Python.
web2py provee de un mecanismo para declarar cules funciones son pblicas y cules
requieren autenticacin y autorizacin. La API de Control de Acceso Basado en Roles
incorporada permite a los desarrolladores la restriccin de acceso a cualquier funcin
basada en autenticacin simple ( login ), pertenencia a grupo o permisos basados en
grupos. La permisologa es minuciosa y se puede combinar con filtros de la base de
datos para permitir, por ejemplo, el acceso a un conjunto especfico de tablas y/o
registros. web2py adems permite el uso de direcciones URL con firma digital y provee
de una API para firmar digitalmente solicitudes con ajax.

Las caractersticas de web2py se han analizado en funcin de su seguridad y los resultados se


pueden consultar en ref.[pythonsecurity].

El paquete y su contenido
Puedes descargar web2py desde el sitio oficial:
http://www.web2py.com

web2py est compuesto por los siguientes componentes:


o

libreras: proveen de las funcionalidades del ncleo de web2py y se puede acceder a


ellas en forma programtica.
servidor web: el servidor web WSGI Rocket.

la aplicacin admin: se usa para crear, disear y administrar las dems


aplicaciones. admin provee de un completo Entorno de Integrado de Desarrollo (IDE)
para la creacin de aplicaciones web2py. Adems incluye otras funcionalidades, como el
entorno de pruebas para interfaz web y la consola shell para navegador.

la aplicacin examples: contiene documentacin y ejemplos interactivos. examples es


un clon del sitio web oficial, e incluye la documentacin epydoc.

la aplicacin welcome: la plantilla de andamiaje para cualquier otra aplicacin. Por


defecto, incluye un men desplegable escalonado CSS y un sistema de autenticacin
(que se detalla en el Captulo 9).

web2py se distribuye como cdigo fuente, y en formato binario para Microsoft Windows y para
Mac OS X.
La distribucin de cdigo fuente se puede usar en cualquier plataforma que corra Python e
incluye los componentes mencionados ms arriba. Para correr el cdigo fuente, necesitas
tener instalado previamente Python 2.5 o 2.7 en el sistema. Adems necesitas alguno de los
motores de bases de datos soportados instalado. Para pruebas y aplicaciones de baja
demanda, puedes usar la base de datos SQLite, que se incluye en las instalaciones de Python
2.7.
Las versiones binarias de web2py (para Windows y Mac OS X) incluyen un intrprete de
Python 2.7 y la base de datos SQLite). Tcnicamente hablando, estos no son componentes de
web2py. El incluirlos en la distribucin binaria te permite utilizar web2py en forma instantnea.
La siguiente imagen ilustra la estructura aproximada de web2py:

En la parte inferior se puede encontrar el intrprete. Si nos desplazamos hacia arriba


encontramos el servidor web (rocket), las libreras, y las aplicaciones. Cada aplicacin cuenta
con su propio diseo MVC (modelos, controladores, vistas, idiomas, bases de datos, y
archivos estticos). Cada aplicacin incluye su propia interfaz de administracin de la base de
datos (appadmin). Cada instancia de web2py incluye por defecto tres aplicaciones: welcome
(la app de andamiaje), admin (el IDE para navegador), y examples (la copia del sitio web y los
ejemplos).

Acerca de este libro


Este libro incluye los siguientes captulos, adems de su introduccin:
o

Captulo 2: es una introduccin minimalista a Python. Se supone que el lector conoce


tanto los conceptos bsicos de programacin estructurada como de programacin
orientada a objetos, como por ejemplo las nociones de bucle, condicin, funcin y clase,
y describe la sintaxis bsica de Python. Adems se agregan ejemplos para mdulos de

Python que se usan en el resto del libro. Si ya conoces Python, puedes omitir este
captulo.
o

Captulo 3: muestra como se inicia web2py, describe la interfaz administrativa y gua al


lector a travs de varios ejemplos que van creciendo en complejidad: una aplicacin que
devuelve una cadena, una aplicacin para conteo, un blog de imgenes, una wiki
completa y con caractersticas avanzadas que permite subir imgenes y hacer
comentarios, provee de un servicio de autenticacin, autorizacin, webservices y una
fuente RSS. Mientras lees este captulo, puedes consultar el Captulo 2 como referencia
para la sintaxis general de Python o el resto de los captulos para una referencia ms
detallada sobre las caractersticas que se usan.

Captulo 4: describe en una forma ms sistemtica la estructura del ncleo y sus


libreras: traduccin de URL, solicitudes, respuestas, cach, planificador, tareas en
segundo plano, traduccin automtica y el flujo de trabajo general o workflow.

Captulo 5: es una gua de referencia para el lenguaje de plantillas usado para la


creacin de vistas. Muestra cmo embeber cdigo de Python en HTML, e ilustra el uso
de los ayudantes (objetos que pueden crear HTML).

Captulo 6: describe la Capa de Abtraccin de la Base de Datos, o DAL. La sintaxis de


DAL se expone por medio de una serie de ejemplos.

Captulo 7: describe los formularios, su validacin y procesamiento. FORM es el


ayudante de bajo nivel para creacin de formularios. SQLFORM es el creador de
formularios de alto nivel. En el Captulo 7 adems se describe la API para
Crear/Leer/Modificar/Borrar (CRUD).

Captulo 8: describe las funcionalidades de comunicaciones como el envo de correo y


SMS y la lectura de bandejas de correo.

Captulo 9: describe la autenticacin y autorizacin y el mecanismo ampliable para


Control de Acceso disponible en web2py. Adems incluye la configuracin de Mail (para
correo electrnico) y CAPTCHA, ya que se usan en conjunto con el sistema de
autenticacin. En la tercera edicin del libro hemos agregado un detalle pormenorizado
de la integracin con sistemas de autenticacin de terceros como OpenID, OAuth,
Google, Facebook, LinkedIn, etc.

Captulo 10: trata sobre la creacin de webservices con web2py. Daremos ejemplos de
integracin con el Google Web Toolkit a travs de Pyjamas, y tambin para Adebe Flash
y PyAMF.

Captulo 11: incluye recetas para jQuery y web2py. web2py se dise principalmene
para la programacin del lado del servidor, pero incluye jQuery, ya que hemos
comprobado que es la mejor librera de JavaScript de cdigo abierto disponible para la
creacin de efectos y el uso de Ajax. En este captulo, se describe el uso eficiente de
jQuery en combinacin con web2py.

Captulo 12: describe los componentes y plugin de web2py como herramientas para la
creacin de aplicaciones modulares. Crearemos un plugin como ejemplo que implementa
funcionalidades de uso comn, como la generacin de grficas, comentarios y el uso de
etiquetas.

Captulo 13: trata sobre la implementacin en produccin de aplicaciones web2py. Se


trata en especial sobre la implementacin en servidores web LAMP (considerada la
alternativa de ms peso). Se describen adems servicios alternativos y la configuracin
de la base de datos PostgreSQL. Tambin se detalla cmo correr web2py como un
servicio en un entorno Microsoft Windows, y la implementacin en algunas plataformas
especficas incluyendo Google App Engine, Heroku, y PythonAnywhere. Adems, en este
captulo, hablaremos sobre cuestiones de escalabilidad y seguridad.

Captulo 14: contiene varias recetas para realizar otras tareas especficas, incluyendo
los upgrade (actualizaciones
del
ncleo),geocoding (aplicaciones
geogrficas),
paginacin, la API de Twitter, y ms.

Este libro slo cubre las funcionalidades bsicas y la API incorporada de web2py. Por otra
parte, este libro no cubre las appliances (es decir, aplicaciones llave en mano).
Puedes descargar esas aplicaciones desde el sitio web [appliances].
Se puede encontrar informacin complementaria en los hilos del grupo de
usuarios[usergroup] (grupo de usuarios de web2py). Tambin est disponible como referencia
AlterEgo[alterego], el antiguo blog y listado de preguntas frecuentes de web2py.
Este libro fue escrito utilizando la sintaxis markmin y fue automticamente convertido a los
formatos HTML, LaTex y PDF.

Soporte
El canal principal para obtener soporte es el grupo de usuarios [usergroup], con docenas de
publicaciones diarias. Incluso si eres un novato, no dudes en consultar - ser un placer
ayudarte.
Tambin disponemos de un sistema para el reporte de problemas (issue tracker)
en http://code.google.com/p/web2py/issues . Por ltimo pero no menos importante, puedes
obtener soporte profesional (consulta la pgina web para ms detalles).

Cmo contribuir
Toda ayuda es realmente apreciada. Puedes ayudar a otros usuarios en el grupo de usuarios,
o
directamente
enviando
parches
del
programa
(en
el
sitio
de
GitHub https://github.com/web2py/web2py). Incluso si encuentras un error de edicin en este
libro, o quieres proponer una mejora, la mejor forma de ayudar es haciendo un parche del libro
en s (que est en https://github.com/mdipierro/web2py-book).

Normas de estilo
PEP8 [style] contiene un compendio de buenas prcticas de estilo para programar en Python.
Encontrars que web2py no siempre sigue estas reglas. Esto no se debe a omisiones o
negligencia; nosotros creemos que los usuarios de web2py deberan seguir estas reglas y los
alentamos a hacerlo. Hemos preferido no seguir algunas de estas reglas cuando definimos
objetos ayudantes con el propsito de minimizar la posibilidad de conflictos con los nombres
de objetos creados por el usuario.
Por ejemplo, la clase que representa un <div> se llama DIV , mientras que segn las reglas
de estilo de Python se la debera haber llamado Div . Creemos que, para el ejemplo
especfico dado, el uso de maysculas en toda la palabra "DIV" es una eleccin ms natural.
Ms an, esta tcnica le da libertad a los programadores para que puedan crear una clase
llamada "Div", si as lo quisieran. Nuestra sintaxis adems, coincide naturalmente con la
notacin DOM de la mayora de los navegadores (incluyendo, por ejemplo, Firefox).
Segn la gua de estilo de Pyhton, todas las cadenas maysculas se deberan usar para
constantes, no para variables. Siguiendo con nuestro ejemplo, incluso considerando
que DIV es una clase, se trata de una clase especial que nunca debera ser modificada por el
usuario porque el hacerlo implicara una incompatibilidad con otras aplicaciones de web2py.
Por lo tanto, creemos que esto autoriza el uso de DIV porque esa clase debera tratarse como
constante, justificandose de esta forma la notacin elegida.
En resumen, se siguen las siguientes convenciones:
o

Los ayudantes HTML se notan con maysculas para toda la palabra como se describi
anteriormente (por ejemplo DIV , A , FORM , URL ).

El objeto de traduccin T se escribe con mayscula aunque se trate de una instancia


de la clase, no la clase en s. Lgicamente, el objeto traductor realiza una accin similar a
la de los ayudantes de HTML; este objeto opera en parte de la conversin ( rendering) de
la informacin presentada. Adems, T debe ser fcil de identificar en el cdigo fuente y
debe tener un nombre corto.

Las clases de la DAL siguen el estndar de estilo de Python (mayscula en la primera


letra), por ejemplo Table , Field , Query , Row , Rows , etc.

Para el resto de los casos, creemos que hemos seguido en lo posible, la gua de estilo de
Python (PEP8). Por ejemplo, todas las instancias de objetos son en minsculas (request,
response, session, cache), y todas las clases internas tienen maysculas iniciales.
En todos los ejemplos de este libro, las palabras clave de web2py se muestran en negrita,
mientras que las cadenas y comentarios se muestran en letra itlica o inclinada.

Licencia
web2py se distribuye segn los trminos de la licencia LGPL versin 3. El texto completo de la
licencia est disponible en ref.[lgpl3]
En conformidad con LGPL puedes:
o

redistribuir web2py con tus aplicaciones (incluyendo las versiones binarias oficiales)

distribuir tus aplicaciones usando las libreras oficiales de web2py segn la licencia
que elijas.

Pero tienes la obligacin de:


o

disponer de las aclaraciones necesarias para dar noticia del uso de web2py en la
documentacin
distribuir toda modificacin de las libreras de web2py con licencia LGPLv3.

La licencia incluye las aclaraciones y advertencias usuales:


NO HAY GARANTAS PARA EL PROGRAMA, EN LA MEDIDA PERMITIDA POR LA LEY
APLICABLE. EXCEPTO CUANDO SE INDIQUE LO CONTRARIO POR ESCRITO, LOS
TITULARES DEL COPYRIGHT Y/U OTRAS PARTES PROPORCIONAN EL PROGRAMA
"TAL CUAL" SIN GARANTAS DE NINGN TIPO, YA SEAN EXPRESAS O IMPLICADAS,
INCLUYENDO, PERO NO LIMITADO A, LAS GARANTAS IMPLCITAS DE
COMERCIALIZACIN Y APTITUD PARA UN PROPSITO PARTICULAR. EL RIESGO EN
CUANTO A LA CALIDAD Y RENDIMIENTO DEL PROGRAMA QUEDA BAJO SU
RESPONSABILIDAD. SI EL PROGRAMA ES DEFECTUOSO, USTED ASUME EL COSTO DE
TODO SERVICIO, REPARACIN O CORRECCIN.
EN NINGN CASO, A MENOS QUE LO EXIJA LA LEY APLICABLE O QUE SEA ACORDADO
POR ESCRITO, UN TITULAR DE DERECHO DE AUTOR O CUALQUIER OTRA PARTE QUE
MODIFIQUE Y/O TRANSMITA EL PROGRAMA COMO SE PERMITE ARRIBA, SER
RESPONSABLE ANTE USTED POR DAOS, INCLUYENDO CUALQUIER DAO GENERAL,
ESPECIAL, INCIDENTAL O DAOS DERIVADOS DEL USO O IMPOSIBILIDAD DE USAR EL
PROGRAMA, INCLUYENDO PERO NO LIMITADO A, LA PRDIDA O DAO DE DATOS, LAS

PRDIDAS SUFRIDAS POR USTED O TERCEROS O UN FALLO DEL PROGRAMA PARA


OPERAR CON CUALQUIER OTRO PROGRAMA, INCLUSO SI EL PROPIETARIO O PARTE
DE OTRO TIPO FUE INFORMADA SOBRE LA POSIBILIDAD DE TALES DAOS.
Primeras anteriores
Las primeras versiones de web2py, 1.0.*-1.90.*, se publicaron bajo la licencia GPL2 con una
clusula adicional para fines comerciales que, por razones prcticas, era muy similar a la
actual licencia LGPLv3.
Software de terceros distribuido en conjunto con web2py
web2py contiene software de terceros en la carpeta gluon/contrib/ y varios archivos de
JavaScript y CSS. Estos archivos se distribuyeron con web2py segn los trminos de sus
licencias originales, como se detalla en los respectivos archivos.

Agradecimientos
web2py fue originalmente desarrollado y registrado por Massimo Di Pierro. La primera versin
(1.0) se liber en octubre del ao 2007. Desde entonces ha sido adoptado por muchos
usuarios, y algunos de ellos tambin han contribuido con reportes de fallas, pruebas,
depuracin, parches, y la correccin de este libro.
Algunos de los desarrolladores y colaboradores ms importantes son, en orden alfabtico,
ordenados por el primer nombre:
web2py contiene cdigo de los siguientes autores, a los que quisera tambin agradecer:
Guido van Rossum por Python[python], Peter Hunt, Richard Gordon, Timothy Farrell por el servidor
web Rocket[rocket] , Christopher Dolivet por EditArea[editarea], Bob Ippolito por simplejson[simplejson], Simon
Cusack y Grant Edwards por pyRTF[pyrtf], Dalke Scientific Software por pyRSS2Gen[pyrss2gen], Mark
Pilgrim por feedparser[feedparser], Trent Mick por markdown2 [markdown2], Allan Saddi por fcgi.py, Evan
Martin por el mdulo memcache de Python[memcache], John Resig por jQuery[jquery].
Quiero agradecer a Helmut Epp (Rector de la Universidad DePaul), David Miller (Decano del
Colegio de Computacin y Medios Digitales de la Universidad DePaul), y Estia Eichten
(Miembro de MetaCryption LLC), por su continua confianza y ayuda.
Por ltimo, quisiera agradecer a mi esposa, Claudia, y a mi hijo, Marco, por tolerarme durante
todo el tiempo que he empleado en el desarrollo de web2py, intercambiando correos con
usuarios y colaboradores, y escribiendo este libro. Este libro est dedicado a ellos.

El lenguaje Python
Acerca de Python

Python es un lenguaje de programacin multipropsito de alto nivel Su filosofa de diseo


enfatiza la productividad del programador y la legibilidad del cdigo. Tiene un ncleo sintctico
minimalista con unos pocos comandos bsicos y simple semntica, pero adems tiene una
enorme y variada librera estndar, que incluye una Interfaz de Programacin de Aplicaciones
(API) API para muchas de las funciones en el nivel del sistema operativo (OS). El cdigo
Python, aunque minimalista, define objetos incorporados como listas enlazadas ( list ), tuplas
( tuple ), tablas hash ( dict ), y enteros de longitud arbitraria ( long ).
Python soporta mltiples paradigmas de programacin, incluyendo programacin orientada a
objetos ( class ), programacin imperativa ( def ) y funcional ( lambda ). Python tiene un
sistema de tipado dinmico y manejo automatizado de memoria utilizando conteo de
referencias (similar a Perl, Ruby y Scheme).
Python fue publicado por primera vez por Guido Van Rossum en 1991. El lenguaje tiene un
modelo abierto de desarrollo basado en la comunidad administrado por la organizacin sin
fines de lucro Python Software Foundation. Existen varios intrpretes y compiladores que
implementan el lenguaje Python, incluyendo uno en Java (Jython) pero, en esta corta revisin,
vamos a centrarnos en la implementacin en C creada por Guido.
Puedes encontrar varios tutoriales, la documentacin oficial y la referencia de las libreras del
lenguaje en el sitio web oficial de Python.[python]
Para referencia adicional sobre Python, podemos recomendar los libros en ref.

[guido]

y ref.[lutz].

Puedes saltarte este captulo si ya tienes experiencia con el lenguaje Python.

Comenzando
Las distribuciones binarias de web2py para Microsoft Windows o Apple OS X vienen
empaquetadas con el intrprete de Python incorporado en el mismo archivo de la distribucin.
Puedes iniciarlo en Windows con el siguiente comando (escribe en prompt/consola del DOS):
web2py.exe -S welcome

Sobre Apple OS X, ingresa el siguiente comando en una ventana de terminal (suponiendo que
ests en la misma carpeta que web2py.app):
./web2py.app/Contents/MacOS/web2py -S welcome

En una mquina con Linux u otro Unix, probablemente ya tengas instalado Python. Si es as,
en el prompt de la shell escribe:

python web2py.py -S welcome

Si no tienes Python 2.5 (o las posteriores 2.x) pre-instalado, tendrs que descargarlo e
instalarlo antes de correr web2py.
La opcin -S welcome de lnea de comandos ordena a web2py que ejecute la shell interactiva
como si los comandos se ejecutaran en un controlador para la aplicacin welcome, la
aplicacin de andamiaje de web2py. Esto pone a tu disposicin casi todas las clases, objetos y
funciones de web2py. Esta es la nica diferencia entre la lnea de comando interactiva de
web2py y la lnea de comando normal de Python.
La interfaz administrativa adems provee de una shell basada en web para cada aplicacin.
Puedes acceder a la de la aplicacin "welcome" en:
http://127.0.0.1:8000/admin/shell/index/welcome

Puedes seguir todos los ejemplos en este captulo utilizando una shell normal o la shell para
web.

help, dir
El lenguaje Python provee de dos comandos para obtener documentacin sobre objetos
definidos en el scope actual, tanto los incorporados como los definidos por el usuario.
Podemos pedir ayuda ( help ) acerca de un objeto, por ejemplo "1":
>>> help(1)
Help on int object:

class int(object)
| int(x[, base]) -> integer
|
| Convert a string or number to an integer, if possible. A floating point
| argument will be truncated towards zero (this does not include a string
| representation of a floating point number!) When converting a string, use
| the optional base. It is an error to supply a base when converting a
| non-string. If the argument is outside the integer range a long object

| will be returned instead.


|
| Methods defined here:
|
| __abs__(...)
|

x.__abs__() <==> abs(x)

...

y, como "1" es un entero, obtenemos una descripcin de la clase int y de todos sus mtodos.
Aqu la salida fue truncada porque es realmente larga y detallada.
En forma similar, podemos obtener una lista de mtodos del objeto "1" con el comando dir :
>>> dir(1)
['__abs__', ..., '__xor__']

Tipos
Python es un lenguaje de tipado dinmico, o sea que las variables no tienen un tipo y por lo
tanto no deben ser declaradas. Los valores, sin embargo, tienen tipo. Puedes consultar a una
variable el tipo de valor que contiene:
>>> a = 3
>>> print type(a)
<type 'int'>
>>> a = 3.14
>>> print type(a)
<type 'float'>
>>> a = 'hola Python'
>>> print type(a)
<type 'str'>

Python adems incluye, como caractersticas nativas, estructuras de datos como listas y
diccionarios.

str

Python soporta el uso de dos diversos tipo de cadenas: ASCII y Unicode. Las cadenas ASCII
se delimitan por '...', "..." o por '...' o """...""". Las comillas triples delimitan cadenas multilnea.
Las cadenas Unicode comienzan con un u seguido por la cadena conteniendo caracteres
Unicode. Una cadena Unicode puede convertirse en una cadena ASCII seleccionando un una
codificacin por ejemplo:
>>> a = 'esta es una cadena ASCII'
>>> b = u'esta es una cadena Unicode'
>>> a = b.encode('utf8')

Al ejecutar estos tres comandos, la a resultante es una cadena ASCII que almacena
caracteres codificados con UTF8. Por diseo, web2py utiliza cadenas codificadas con UTF8
internamente.
Adems es posible utilizar variables en cadenas de distintas formas:
>>> print 'el nmero es ' + str(3)
el nmero es 3
>>> print 'el nmero es %s' % (3)
el nmero es 3
>>> print 'el nmero es %(numero)s' % dict(numero=3)
el nmero es 3

La ltima notacin es ms explcita y menos propensa a errores, y es la recomendada.


Muchos objetos de Pyhton, por ejemplo nmeros, pueden ser serializados en cadenas
utilizando str o repr . Estos dos comandos son realmente similares pero producen una salida
ligeramente diferente. Por ejemplo:
>>> for i in [3, 'hola']:
print str(i), repr(i)
33
hola 'hola'

Para las clases definidas por el usuario, str y repr pueden definirse/redefinirse utilizando los
operadores especiales __str__ y __repr__ . Estos se describirn bsicamente ms adelante;

para mayor informacin, consulta la documentacin oficial de Python

[pydocs]

. repr siempre tiene

un valor por defecto.


Otra caracterstica importante de una cadena de Python es que, como una lista, es un
objeto iterable
>>> for i in 'hola':
print i
h
o
l
a

list

Los mtodos principales de una lista de Python son append, insert, y delete:
>>> a = [1, 2, 3]
>>> print type(a)
<type 'list'>
>>> a.append(8)
>>> a.insert(2, 7)
>>> del a[0]
>>> print a
[2, 7, 3, 8]
>>> print len(a)
4

Las listas se pueden cortar ( slice ):


>>> print a[:3]
[2, 7, 3]
>>> print a[1:]
[7, 3, 8]

>>> print a[-2:]


[3, 8]

y concatenar:
>>> a = [2, 3]
>>> b = [5, 6]
>>> print a + b
[2, 3, 5, 6]

Una lista es iterable; puedes recorrerla en un bucle:


>>> a = [1, 2, 3]
>>> for i in a:
print i
1
2
3

Los elementos de una lista no tienen que ser del mismo tipo; pueden ser de cualquier tipo de
objeto de Python.
Hay una situacin muy comn en la que se puede usar una lista por comprensin o list
comprehension . Consideremos el siguiente cdigo:
>>> a = [1,2,3,4,5]
>>> b = []
>>> for x in a:
if x % 2 == 0:
b.append(x * 3)
>>> b
[6, 12]

Este cdigo claramente procesa una lista de tems, separa y modifica un subconjunto de la
lista ingresada y crea una nueva lista resultante, y este cdigo puede ser enteramente
reemplazado por la siguiente lista por comprensin:

>>> a = [1,2,3,4,5]
>>> b = [x * 3 for x in a if x % 2 == 0]
>>> b
[6, 12]

tuple

Una tupla es como una lista, pero su tamao y elementos son inmutables, mientras que en
una lista son mutables. Si un elemento de una tupla es un objeto, los atributos del objeto son
mutables. Una tupla est delimitada por parntesis.
>>> a = (1, 2, 3)

Entonces si esto funciona para una lista:


>>> a = [1, 2, 3]
>>> a[1] = 5
>>> print a
[1, 5, 3]

la asignacin a un elemento no funciona para una tupla:


>>> a = (1, 2, 3)
>>> print a[1]
2
>>> a[1] = 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

Una tupla, como en la lista, es un objeto iterable. Ntese que una tupla que consista de un
elemento debe incluir una coma al final, como se muestra abajo:
>>> a = (1)
>>> print type(a)
<type 'int'>

>>> a = (1,)
>>> print type(a)
<type 'tuple'>

Las tuplas son realmente tiles para ordenar objetos en grupos eficientemente por su
inmutabilidad, y los parntesis son a veces opcionales:
>>> a = 2, 3, 'hola'
>>> x, y, z = a
>>> print x
2
>>> print z
hola

dict

Un dict (diccionario) de Python es una tabla hash que asocia ( map ) un objeto-clave a un
objeto-valor. Por ejemplo:
>>> a = {'k':'v', 'k2':3}
>>> a['k']
v
>>> a['k2']
3
>>> a.has_key('k')
True
>>> a.has_key('v')
False

Las claves pueden ser de cualquier tipo apto para tabla hash (int, string, o cualquier objeto
cuya clase implemente el mtodo __hash__ ). Los valores pueden ser de cualquier tipo. Las
claves y valores diferentes en el mismo diccionario no tienen que ser de un nico tipo. Si las
claves son caracteres alfanumricos, el diccionario tambin se puede declarar con una sintaxis
alternativa:
>>> a = dict(k='v', h2=3)

>>> a['k']
v
>>> print a
{'k':'v', 'h2':3}
has_key , keys , values y items son mtodos tiles:
>>> a = dict(k='v', k2=3)
>>> print a.keys()
['k', 'k2']
>>> print a.values()
['v', 3]
>>> print a.items()
[('k', 'v'), ('k2', 3)]

El mtodo items produce una lista de tuplas, cada una conteniendo una clave y su valor
asociado.
>>> a = [1, 2, 3]
>>> del a[1]
>>> print a
[1, 3]
>>> a = dict(k='v', h2=3)
>>> del a['h2']
>>> print a
{'k':'v'}

Internamente, Python utiliza el operador hash para convertir objetos en enteros, y usa ese
entero para determinar dnde almacenar el valor.
>>> hash("hola mundo")
-1500746465

Acerca del espaciado


Python usa espaciado/sangra para delimitar bloques de cdigo. Un bloque de cdigo
comienza con una lnea que finaliza con dos puntos, y contina para todas las lneas que
tengan igual o mayor espaciado que la prxima lnea. Por ejemplo:
>>> i = 0
>>> while i < 3:
>>>

print i

>>>

i=i+1

>>>
0
1
2

Es comn el uso de cuatro espacios para cada nivel de espaciado o indentation. Es una buena
prctica no combinar la tabulacin con el espacio, porque puede resultar (invisiblemente)
confuso.

for...in

En Python, puedes recorrer objetos iterables en un bucle


>>> a = [0, 1, 'hola', 'python']
>>> for i in a:
print i
0
1
hola
python

Un atajo usual es xrange , que genera un rango iterable sin almacenar la lista entera de
elementos.
>>> for i in xrange(0, 4):

print i
0
1
2
3

Esto es equivalente a la sintaxis de C/C++/C#/Java:


for(int i=0; i<4; i=i+1) { print(i); }

Otro comando de utilidad es enumerate , que realiza un conteo mientras avanza el bucle:
>>> a = [0, 1, 'hola', 'python']
>>> for i, j in enumerate(a):
print i, j
00
11
2 hola
3 python

Tambin hay un keyword range(a, b, c) que devuelve una lista de enteros comenzando con el
valor a y con un incremento de c , y que finaliza con el ltimo valor menor a b . Por
defecto, a es 0 y c es 1. xrange es similar es similar pero en realidad no genera una lista,
slo un iterator para la lista; que es ms apropiado para crear estos bucles.
Se puede salir de un bucle utilizando break
>>> for i in [1, 2, 3]:
print i
break
1

Puedes saltar a la prxima iteracin del bucle sin ejecutar todo el bloque de cdigo
con continue
>>> for i in [1, 2, 3]:

print i
continue
print 'test'
1
2
3

while

El bucle while en Python opera bsicamente como lo hace en otros lenguajes de


programacin, iterando una cantidad indefinida de veces y comprobando una condicin antes
de cada iteracin. Si la condicin es False , el bucle finaliza.
>>> i = 0
>>> while i < 10:
i=i+1
>>> print i
10

No hay una instruccin especial loop...until en Python.

if...elif...else

El uso de condicional en Python es intuitivo:


>>> for i in range(3):
>>>
>>>
>>>
>>>
>>>
>>>
cero

if i == 0:
print 'cero'
elif i == 1:
print 'uno'
else:
print 'otro'

uno
otro

"elif" significa "else if". Tanto elif como else son partes opcionales. Puede haber ms de
una elif pero slo una declaracin else . Se pueden crear condicionales complicados
utilizando los operadores not , and y or .
>>> for i in range(3):
>>>

if i == 0 or (i == 1 and i + 1 == 2):

>>>

print '0 or 1'

try...except...else...finally

Python puede lanzar (throw) - perdn, generar - excepciones (Exception):


>>> try:
>>>

a=1/0

>>> except Exception, e:


>>>

print 'epa: %s' % e

>>> else:
>>>

print 'sin problemas aqu'

>>> finally:
>>>

print 'listo'

epa: integer division or modulo by zero


listo

Si la excepcin se genera (raise), es atrapada por la clusula except , que es ejecutada; no se


ejecuta en cambio la clusula else . Si no se genera ninguna excepcin, la clusula
de except no se ejecuta, pero en cambio la de else s. La clusula de finally se ejecuta
siempre. Puede haber mltiples clusulas except para distintas excepciones posibles:
>>> try:
>>>

raise SyntaxError

>>> except ValueError:

>>>

print 'error en el valor'

>>> except SyntaxError:


>>>

print 'error sintctico'

error sintctico

Las clusulas else y finally son opcionales.


Aqu mostramos una lista
BaseException
+-- HTTP (defined by web2py)
+-- SystemExit
+-- KeyboardInterrupt
+-- Exception
+-- GeneratorExit
+-- StopIteration
+-- StandardError
|

+-- ArithmeticError

+-- FloatingPointError

+-- OverflowError

+-- ZeroDivisionError

+-- AssertionError

+-- AttributeError

+-- EnvironmentError

+-- IOError

+-- OSError

+-- WindowsError (Windows)

+-- VMSError (VMS)

+-- EOFError

+-- ImportError

+-- LookupError

+-- IndexError

+-- KeyError

+-- MemoryError

+-- NameError

+-- ReferenceError

+-- RuntimeError

+-- SyntaxError

+-- SystemError

+-- TypeError

+-- ValueError

+-- UnicodeDecodeError

+-- UnicodeEncodeError

+-- UnicodeTranslateError

+-- UnboundLocalError

+-- NotImplementedError

+-- IndentationError
+-- TabError

+-- UnicodeError

+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarning
+-- ImportWarning
+-- UnicodeWarning

Para una descripcin detallada de cada una, consulta la documentacin oficial de Python.

web2py expone slo una nueva excepcin, llamada HTTP . Cuando es generada, hace que el
programa devuelva una pgina de error HTTP (para ms sobre este tema consulta el Captulo
4).
Cualquier objeto puede ser utilizado para generar una excepcin, pero es buena prctica
generar excepciones con objetos que extienden una de las clases de excepcin incorporadas.

def...return

Las funciones se declaran utilizando def . Aqu se muestra una funcin de Python tpica:
>>> def f(a, b):
return a + b
>>> print f(4, 2)
6

No hay necesidad (o forma) de especificar los tipos de los argumentos ni el tipo o tipos
devueltos. En este ejemplo, se define una funcin f para que tome dos argumentos.
Las funciones son la primer caracterstica sintctica descripta en este captulo para introducir
el concepto de "scope" (alcance/mbito), o "namespace" (espacio de nombres). En el ejemplo
de arriba, los identificadores ( identifier ) a y b son indefinidos fuera del scope de la
funcin f :
>>> def f(a):
return a + 1
>>> print f(1)
2
>>> print a
Traceback (most recent call last):
File "<pyshell#22>", line 1, in <module>
print a
NameError: name 'a' is not defined

Los identificadores definidos por fuera del scope de una funcin son accesibles dentro de la
funcin; ntese cmo el identificador a es manejado en el siguiente cdigo:

>>> a = 1
>>> def f(b):
return a + b
>>> print f(1)
2
>>> a = 2
>>> print f(1) # se usa un nuevo valor para a
3
>>> a = 1 # redefine a
>>> def g(b):
a = 2 # crea un nuevo a local
return a + b
>>> print g(2)
4
>>> print a # el a global no ha cambiado
1

Si se modifica a , las siguientes llamadas a la funcin usarn el nuevo valor del a global
porque la definicin de la funcin enlaza la ubicacin de almacenamiento del identificador a ,
no el valor de a mismo al momento de la declaracin de la funcin; sin embargo, si se asigna
a a dentro de la funcin g , la a global no es afectada porque la nueva a local protege el
valor global. La referencia del scope externo puede ser utilizada en la creacin de "cierres"
(closures):
>>> def f(x):
def g(y):
return x * y
return g
>>> duplicador = f(2) # duplicador es una nueva funcin
>>> triplicador = f(3) # triplicador es una nueva funcin
>>> cuadruplicador = f(4) # cuadruplicador es una nueva funcin

>>> print duplicador(5)


10
>>> print triplicador(5)
15
>>> print cuadruplicador(5)
20

La funcin f crea nuevas funciones; y ntese que el scope del nombre g es enteramente
interno a f . Los cierres son extremadamente poderosos.
Los argumentos de funcin pueden tener valores por defecto, y pueden devolver mltiples
resultados:
>>> def f(a, b=2):
return a + b, a - b
>>> x, y = f(5)
>>> print x
7
>>> print y
3

Los argumentos de las funciones pueden pasarse explcitamente por nombre, y esto significa
que el orden de los argumentos especificados en la llamada puede ser diferente del orden de
los argumentos con los que la funcin fue definida:
>>> def f(a, b=2):
return a + b, a - b
>>> x, y = f(b=5, a=2)
>>> print x
7
>>> print y
-3

Las funciones tambin pueden aceptar un nmero variable de argumentos en tiempo de


ejecucin:
>>> def f(*a, **b):
return a, b
>>> x, y = f(3, 'hola', c=4, test='mundo')
>>> print x
(3, 'hola')
>>> print y
{'c':4, 'test':'mundo'}

Aqu los argumentos no pasados por nombre (3, 'hola') se almacenan en la tupla a , y los
argumentos pasados por nombre ( c y test ) se almacenan en el diccionario b .
En el caso opuesto, puede pasarse una lista o tupla a una funcin que requiera una conjunto
ordenado de argumentos para que los "abra" (unpack):
>>> def f(a, b):
return a + b
>>> c = (1, 2)
>>> print f(*c)
3

y un diccionario se puede "abrir" para pasar argumentos por nombre:


>>> def f(a, b):
return a + b
>>> c = {'a':1, 'b':2}
>>> print f(**c)
3

lambda
lambda presenta una forma de declarar en forma fcil y abreviada funciones sin nombre:
>>> a = lambda b: b + 2

>>> print a(3)


5

La expresin " lambda [a]:[b]" se lee exactamente como "una funcin con argumentos [a] que
devuelve [b]". La expresin lambda es annima, pero la funcin adquiere un nombre al ser
asignada a un identificador a . Las reglas de espacios de nombres para def tambin son
igualmente vlidas para lambda , y de hecho el cdigo de arriba, con respecto a a es idntico
al de la declaracin de la funcin utilizando def :
>>> def a(b):
return b + 2
>>> print a(3)
5

La nica ventaja de lambda es la brevedad; sin embargo, la brevedad puede ser muy
conveniente en ciertas ocasiones. Consideremos una funcin llamada map que aplica una
funcin a todos los tems en una lista, creando una lista nueva:
>>> a = [1, 7, 2, 5, 4, 8]
>>> map(lambda x: x + 2, a)
[3, 9, 4, 7, 6, 10]

Este cdigo se hubiese duplicado en tamao si utilizbamos def en lugar de lambda . El


problema principal de lambda es que (en la implementacin de Python) la sintaxis permite
una slo una expresin simple; aunque, para funciones ms largas, puede utilizarse def y el
costo extra de proveer un nombre para la funcin disminuye cuando el tamao de la funcin
aumenta. Igual que con def , lambda puede utilizarse para "condimentar" una funcin: las
nuevas funciones se pueden crear envolviendo funciones existentes de manera que la nueva
funcin tome un conjunto distinto de argumentos:
>>> def f(a, b): return a + b
>>> g = lambda a: f(a, 3)
>>> g(2)
5

Hay muchas situaciones donde es til condimentar, pero una de ellas es especialmente a
propsito en web2py: el manejo del cach. Supongamos que tenemos una funcin pesada que
comprueba si un argumento es nmero primo:
def esprimo(numero):
for p in range(2, numero):
if (numero % p) == 0:
return False
return True

Esta funcin obviamente consume mucho tiempo de proceso.


Supongamos que tenemos una funcin de cach cache.ram que toma tres argumentos: una
clave, una funcin y una cantidad de segundos.
valor = cache.ram('clave', f, 60)

La primera vez que se llame, llamar a su vez a la funcin f() , almacenar la salida en un
diccionario en memoria (digamos "d"), y lo devolver de manera que el valor es:
valor = d['clave']=f()

La segunda vez que se llame, si la clave est en el diccionario y no es ms antigua del nmero
de segundos especificados (60), devolver el valor correspondiente sin volver a ejecutar la
llamada a la funcin.
valor = d['clave']

Cmo podemos almacenar en el cach la salida de la funcin esprimo para cualquier valor
de entrada? De esta forma:
>>> numero = 7
>>> segundos = 60
>>> print cache.ram(str(numero), lambda: esprimo(numero), segundos)
True
>>> print cache.ram(str(numero), lambda: esprimo(numero), segundos)
True

La salida es siempre la misma, pero la primera vez que se llama a cache.ram , se llama
a esprimo ; la segunda vez, no.

Las funciones de Python, creadas tanto con def como con lambda permiten refactorizar
funciones existentes en trminos de un conjunto distinto de
argumentos. cache.ram y cache.disk son funciones para manejo de cach de web2py.

class

Como Python es un lenguaje de tipado dinmico, las clases y objetos pueden resultar
extraas. De hecho no necesitas definir las variables incluidas (atributos) al declarar una clase,
y distintas instancias de la misma clase pueden tener distintos atributos. Los atributos
(attribute) se asocian generalmente con la instancia, no la clase (excepto cuando se declaran
como atributos de clase o "class attributes", que vienen a ser las "static member variables" de
C++/Java).
Aqu se muestra un ejemplo:
>>> class MiClase(object): pass
>>> miinstancia = MiClase()
>>> miinstancia.mivariable = 3
>>> print miinstancia.mivariable
3

Ntese que pass es un comando que no "hace" nada. En este caso se utiliza para definir una
clase MiClase que no contiene nada. MiClase() llama al constructor de la clase (en este caso
el constructor por defecto) y devuelve un objeto, una instancia de la clase. El (object) en la
definicin de la clase indica que nuestra clase extiende la clase incorporada object . Esto no
es obligatorio, pero se considera una buena prctica.
He aqu una clase ms complicada:
>>> class MiClase(object):
>>>

z=2

>>>

def __init__(self, a, b):

>>>

self.x = a

>>>

self.y = b

>>>
>>>

def sumar(self):
return self.x + self.y + self.z

>>> miinstancia = MiClase(3, 4)


>>> print miinstancia.sumar()
9

Las funciones declaradas adentro de la clase son mtodos. Algunos mtodos tienen nombres
especiales reservados. Por ejemplo, __init__ es el constructor. Todas las variables son
variables locales del mtodo exceptuando las variables declaradas fuera de los mtodos. Por
ejemplo, z es una "variable de clase", que equivale a una "static member variable" de C++
que almacena el mismo valor para toda instancia de la clase.
Hay que tener en cuenta que __init__ toma 3 argumentos y add toma uno, y sin embargo los
llamamos con dos y ningn argumento respectivamente. El primer argumento representa, por
convencin, el nombre local utilizado dentro del mtodo para referirse a el objeto actual, pero
podramos haber utilizado cualquier otro. self cumple el mismo rol que *this en C++
o this en Java, pero self no es una palabra reservada.
Esta sintaxis es necesaria para evitar ambigedad cuando se declaran clases anidadas, como
una clase que es local a un mtodo dentro de otra clase.

Atributos especiales, mtodos y operadores


Los atributos de clase, mtodos y operadores que comienzan con doble guin bajo ( __ ) estn
usualmente como privados (por ejemplo para usarse internamente pero no expuestos fuera de
la clase) aunque esta convencin no est controlada en el intrprete.
Algunos de ellos son palabras reservadas y tienen un significado especial.
Aqu se muestran, como ejemplo, tres de ellos:
o

__len__

__getitem__

__setitem__

Se pueden usar, por ejemplo, para crear un objeto contenedor que se comporta como una
lista:

>>> class MiLista(object):


>>>

def __init__(self, *a): self.a = list(a)

>>>

def __len__(self): return len(self.a)

>>>

def __getitem__(self, i): return self.a[i]

>>>

def __setitem__(self, i, j): self.a[i] = j

>>> b = MiLista(3, 4, 5)
>>> print b[1]
4
>>> b.a[1] = 7
>>> print b.a
[3, 7, 5]

Entre otros operadores especiales estn __getattr__ y __setattr__ , que definen los atributos
get y set para la clase, y __sum__ y __sub__ , que hacen sobrecarga de operadores
aritmticos. Para el uso de estos operadores se pueden consultar textos ms avanzados en
este tema. Ya mencionamos anteriormente los operadores especiales __str__ y __repr__ .

Entrada/salida de archivos
En Python puedes abrir y escribir en un archivo con:
>>> archivo = open('miarchivo.txt', 'w')
>>> archivo.write('hola mundo')
>>> archivo.close()

En forma similar, puedes leer lo escrito en el archivo con:


>>> archivo = open('miarchivo.txt', 'r')
>>> print archivo.read()
hola mundo

Como alternativa, puedes leer en modo binario con "rb", escribir en modo binario con "wb", y
abrir el archivo en modo incremental (append) con "a", utilizando la notacin estndar de C.

El comando de lectura read toma un argumento opcional, que es el nmero de byte. Puedes
adems saltar a cualquier ubicacin en el archivo usando seek .
Puedes recuperar lo escrito en el archivo con read
>>> print archivo.seek(5)
>>> print archivo.read()
mundo

y puedes cerrar el archivo con:


>>> archivo.close()

En la distribucin estndar de Python, conocida como CPython, las variables son


calculadas por referencia ("reference-counting"), incluyendo aquellas que manejan
archivos, as que CPython sabe que cuando le conteo por referencia de un archivo abierto
disminuye hasta cero, el archivo debera cerrarse y la variable descartada. Sin embargo,
en otras implementaciones de Python como PyPy, se usa la recoleccin de basura en
lugar del conteo de referencias, y eso implica que es posible que se acumulen
demasiados manejadores de archivos abiertos al mismo tiempo, resultando en un error
antes que "gc" tenga la opcin cerrarlos y descartarlos a todos. Por eso es mejor cerrar
explcitamente los manejadores de archivos cuando ya no se necesitan. web2py provee
de dos funciones ayudantes, read_file() y write_file() en el espacio de nombres
de gluon.fileutils que encapsulan el acceso a los archivos y aseguran que los
manejadores de archivos en uso se cierren oportunamente.
Cuando utilizas web2py, no sabes dnde se ubica el directorio actual, porque eso
depende de la forma en que se halla configurado el marco de desarrollo. La
variable request.folder contiene la ruta (path) a la aplicacin actual. Las rutas pueden
concatenarse con el comando os.path.join , del hablamos ms adelante.

exec

, eval

A diferencia de Java, Python es realmente un lenguaje interpretado. Esto significa que tiene la
habilidad de ejecutar comandos de Python almacenados en cadenas. Por ejemplo:
>>> a = "print 'hola mundo'"
>>> exec(a)
'hola mundo'

Qu ha ocurrido? La funcin exec le dice al intrprete que se llame a s mismo y ejecute le


contenido de la cadena pasada como argumento. Tambin es posible ejecutar el contenido de
una cadena en el contexto definido por los smbolos de un diccionario:
>>> a = "print b"
>>> c = dict(b=3)
>>> exec(a, {}, c)
3

Aqu el intrprete, cuando ejecuta la cadena a , ve los smbolos definidos en c ( b en el


ejemplo), pero no ve a c o a . Esto no es equivalente a un entorno restringido,
porque exec no impone lmites a lo que el cdigo interior pueda hacer; slo define el conjunto
de variables visibles en el cdigo.
Una funcin relacionada es eval , que hace algo muy parecido a exec , pero espera que el
argumento pasado evale a un valor dado, y devuelve ese valor.
>>> a = "3*4"
>>> b = eval(a)
>>> print b
12

import

El verdadero poder de Python est en sus libreras de mdulos. Ellos proveen un gran y
consistente conjunto de Interfaces de Programacin de Aplicaciones (API) para muchas
libreras del sistema (a menudo en un modo independiente del sistema operativo).
Por ejemplo, si necesitamos utilizar un nmero aleatorio, podemos hacer:
>>> import random
>>> print random.randint(0, 9)
5

Esto imprime un entero aleatorio entre 0 y 9 (incluyendo al 9), 5 en el ejemplo. La


funcin randint est definida en el mdulo random . Adems es posible importar un objeto de
un mdulo en el espacio de nombres actual:

>>> from random import randint


>>> print randint(0, 9)

o importar todo objeto de un mdulo en el espacio de nombres actual:


>>> from random import *
>>> print randint(0, 9)

o importar todo en un nuevo espacio de nombres especfico:


>>> import random as myrand
>>> print myrand.randint(0, 9)

En adelante, vamos a usar objetos


como os , sys , datetime , time y cPickle .

principalmente

definidos

en

mdulos

Todo objeto de Python es accesible a travs de un mdulo llamado gluon y ese es el


tema de captulos posteriores. Internamente, web2py usa muchos mdulos de Python (por
ejemplo thread ), pero slo en raras ocasiones necesitars acceder a ellos en forma
directa.
En las secciones siguientes describiremos aquellos mdulos que son de mayor utilidad.
os

Este mdulo provee de una interfaz a la API del sistema operativo. Por ejemplo:
>>> import os
>>> os.chdir('..')
>>> os.unlink('archivo_a_borrar')

Algunas de las funciones de os , como chdir , NO DEBEN usarse en web2py, porque no


son aptas para el proceso por hilos ("thread-safe").
os.path.join es de gran utilidad; permite concatenar rutas a directorios y archivos en una

forma independiente del sistema operativo:


>>> import os
>>> a = os.path.join('ruta', 'sub_ruta')
>>> print a

ruta/sub_ruta

Las variables de entorno del sistema se pueden examinar con:


>>> print os.environ

que es un diccionario de slo lectura.


sys

El mdulo sys contiene muchas variables y funciones, pero la que ms usamos es sys.path .
Contiene una lista de rutas (path) donde Python busca los mdulos. Cuando intentamos
importar un mdulo, Python lo busca en todos los directorios listados en sys.path . Si instalas
mdulos adicionales en alguna ubicacin y quieres que Python los encuentre, necesitas aadir
(append) en sys.path la ruta o path a esa ubicacin.
>>> import sys
>>> sys.path.append('ruta/a/mis/mdulos')

Cuando corremos web2py, Python permanece residente en memoria, y se configura un


nico sys.path , aunque puede haber varios hilos respondiendo a las solicitudes HTTP. Para
evitar la prdida de espacio en memoria (leak), es mejor comprobar que una ruta ya est
presente antes de aadirla:
>>> ruta = 'ruta/a/mis/mdulos'
>>> if not ruta in sys.path:
sys.path.append(ruta)

datetime

El uso del mdulo datetime es ms fcil de describir por algunos ejemplos:


>>> import datetime
>>> print datetime.datetime.today()
2008-07-04 14:03:90
>>> print datetime.date.today()
2008-07-04

De vez en cuando puedes necesitar una referencia cronolgica para los datos (timestamp)
segn el tiempo de UTC en lugar de usar la hora local. En ese caso puedes usar la siguiente
funcin:
>>> import datetime
>>> print datetime.datetime.utcnow()
2008-07-04 14:03:90

El mdulo datetime contiene varias clases: date (fecha), datetime (fecha y hora), time (hora) y
timedelta. La diferencia entre dos objetos date o dos datetime o dos time es un timedelta:
>>> a = datetime.datetime(2008, 1, 1, 20, 30)
>>> b = datetime.datetime(2008, 1, 2, 20, 30)
>>> c = b - a
>>> print c.days
1

En web2py, date y datetime se usan para almacenar los tipos de datos SQL correspondientes
cuando se pasan desde o a la base de datos.
time

El mdulo time difiere de date o datetime porque representa el tiempo en segundos desde el
epoch (comenzando desde 1970)
>>> import time
>>> t = time.time()
1215138737.571

Consulta la documentacin de Python para otras funciones de conversin entre tiempo en


segundos y tiempo como datetime .
cPickle

Este es un mdulo realmente poderoso. Provee de funciones que pueden serializar casi
cualquier objeto de Python, incluyendo objetos autoreferidos (self-referential). Por ejemplo,
vamos a crear un objeto inusual:
>>> class MiClase(object): pass

>>> miinstancia = MiClase()


>>> miinstancia.x = 'algo'
>>> a = [1 ,2, {'hola':'mundo'}, [3, 4, [miinstancia]]]

y ahora:
>>> import cPickle
>>> b = cPickle.dumps(a)
>>> c = cPickle.loads(b)

En el ejemplo, b is una cadena que representa a , y c es una copia de a generada por la


deserializacin de b .
cPickle puede adems serializar y deserializar desde un archivo:
>>> cPickle.dump(a, open('myfile.pickle', 'wb'))
>>> c = cPickle.load(open('myfile.pickle', 'rb'))

Resumen
Inicio
web2py viene con paquetes binarios para Windows y Mac OS X. Estos paquetes incluyen el
intrprete de Python para que no necesites tenerlo preinstalado. Tambin hay una versin de
cdigo fuente que corre en Mac, Linux, y otros sistemas Unix. Se asume para el paquete de
cdigo fuente que se cuenta con el intrprete de Python instalado en la computadora.
web2py no requiere instalacin. Para iniciarlo, descomprime el archivo zip descargado para tu
sistema operativo especfico y ejecuta el archivo web2py correspondiente.
En Unix y Linux (distribucin de cdigo fuente), ejecuta el siguiente comando:
python web2py.py

En OS X (distribucin binaria), debes correr:


open web2py.app

En Windows (distribucin binaria), corre el siguiente comando:

web2py.exe

En Windows (distribucin de cdigo fuente), ejecuta:


c:/Python27/python.exe web2py.exe

Importante: para correr la distribucin de cdigo fuente de web2py en Windows debes


instalar primero las extensiones de Python para Windows de Mark Hammond
desde http://sourceforge.net/projects/pywin32/
El programa web2py acepta varias opciones de la lnea de comandos que se describen ms
adelante.
Por defecto, al iniciar, web2py muestra una pantalla de inicio y luego un widget de la GUI que
pide que elijas una una contrasea temporaria de administrador, la direccin IP de la interfaz
de red a utilizar para el servidor web, y un nmero de puerto desde el cual se servirn las
solicitudes. Por defecto, web2py corre su servidor web en 127.0.0.1:8000 (puerto 8000 en
localhost), pero puedes correrlo en cualquier direccin IP y puerto disponible. Puedes
consultar la direccin IP de tu interfaz de red abriendo una consola y escribiendo ipconfig en
Windows o ifconfig en OS X y Linux. En adelante asumiremos que web2py est corriendo en
localhost (127.0.0.1:8000). Utiliza 0.0.0.0:80 para correr web2py pblicamente en cualquiera
de tus interfaces de red.

Si no especificas una contrasea administrativa, la interfaz administrativa se deshabilita. Esta


es una medida de seguridad para evitar que la interfaz admin quede expuesta pblicamente.

El acceso a la interfaz administrativa, admin, slo es posible desde localhost a menos que
ests corriendo web2py detrs de Apache con mod_proxy. Si admin detecta un proxy, la
cookie de la sesin se establece como segura y el login de admin no funciona a menos que la
comunicacin entre el cliente y el proxy sea sobre HTTPS; esta es una medida de seguridad.
Todas las comunicaciones entre el cliente y admin deben siempre ser o locales o cifradas; de
lo contrario un atacante podra realizar un ataque tipo "man-in-the-middle" o "replay attack" y
ejecutar cdigo arbitrario en el servidor.
Luego de que la contrasea administrativa ha sido creada, web2py inicia automticamente el
navegador con la direccin:
http://127.0.0.1:8000/

Si la computadora no tiene un navegador por defecto, abre un navegador web e ingresa la


URL.

Al hacer clic sobre "interfaz administrativa" se abre la pgina de login de esa interfaz.

La contrasea del administrador es la seleccionada al inicio del programa. Ten en cuenta que
slo hay un administrador, y por lo tanto slo una contrasea administrativa. Por razones de
seguridad, se pide al desarrollador que ingrese una nueva contrasea cada vez que web2py
inicie a menos que se especifique la opcin <recycle>. Esto es diferente a cmo se manejan
las contraseas en las aplicaciones de web2py.
Luego de que el administrador se autentica en web2py, el navegador es redirigido a la pgina
"site".

Esta pgina lista todas las aplicaciones de web2py instaladas y permite su administracin.
web2py viene con tres aplicaciones:
o

Una aplicacin llamada admin, la que ests utilizando en este momento.

La aplicacin examples, con la documentacin interactiva en lnea y una rplica del


sitio oficial de web2py.

Una aplicacin llamada welcome. Esta es la plantilla bsica para cualquier otra
aplicacin. Se dice que es la aplicacin de andamiaje. Esta es tambin la aplicacin que
da la bienvenida al usuario al inicio del programa.

Las aplicaciones de web2py listas para usar son llamadas "appliances" (artefactos). Hay
muchas appliances disponibles para descarga gratuita en [appliances] . Invitamos a los usuarios de
web2py a que suban nuevas appliances, tanto de cdigo abierto como de cdigo cerrado
(compiladas y empaquetadas).
Desde la pgina "site" de la aplicacin admin, puedes realizar las siguientes operaciones:
o

instalar una aplicacin completando el formulario en la parte inferior derecha de la


pgina. Especifica el nombre de la aplicacin, selecciona el archivo conteniendo una
aplicacin empaquetada o la URL donde se ubica la aplicacin, y haz clic en "submit".

desinstalar una aplicacin haciendo clic en el botn correspondiente. Se muestra una


pgina de confirmacin.

crear una nueva aplicacin especificando un nombre y haciendo clic en "crear".

empaquetar una aplicacin para su distribucin haciendo clic en el botn


correspondiente. La descarga de la aplicacin es un archivo .tar conteniendo todo,
incluyendo la base de datos. No deberas descomprimir este archivo; es
automticamente descomprimido por web2py cuando se instala a travs de admin.

limpiar los archivos temporarios de la aplicacin, como sesiones, errores y archivos de


cach.

habilitar/deshabilitar cada aplicacin. Cuando una aplicacin se deshabilita, no es


posible utilizarla en forma remota pero no se deshabilitar en forma local (para llamadas
desde el mismo sistema). Esto quiere decir que an es posible acceder a las aplicaciones
deshabilitadas detrs de un servidor proxy. Las aplicaciones se deshabilitan creando un
archivo llamado "DISABLED" en la carpeta de la aplicacin. Los usuarios que intenten
acceder a una aplicacin deshabilitada recibirn un error 503 de HTTP. Puedes usar
routes_onerror para personalizar las pginas de error.

EDITAR la aplicacin.

Cuando creas una nueva aplicacin utilizando admin, esta se instala como un clon de la
app de andamiaje "welcome" con un archivo "models/db.py" que crea una base de datos
SQLite, conecta a ella, instancia Auth, Crud, y Service, y los configura. Adems provee un

archivo "controllers/default.py", que expone las acciones "index", "download", "user" para
el manejo de usuarios, y "call" para los servicios. En adelante, asumiremos que estos
archivos se han eliminado; vamos a crear apps de cero.
web2py tambin viene con un asistente, descripto ms adelante en este captulo, que
puede escribir cdigo alternativo de andamiaje automticamente basado en plantillas y
plugin disponibles en la web y basndose en descripciones de alto nivel de los modelos.

Ejemplos sencillos
Di hola
Aqu, como ejemplo, crearemos una app web simple que muestre el mensaje "Hola desde
MiApp" al usuario. Llamaremos a esta aplicacin "miapp". Adems aadiremos un contador
que realice un conteo de cuntas veces el mismo usuario visita la pgina.
Puedes crear una nueva aplicacin con slo escribir su nombre en el formulario de la parte
superior derecha de la pgina site en admin.

Luego de que presiones el botn de [crear], la aplicacin se genera como copia de la


aplicacin welcome incorporada.

Para correr la aplicacin, visita:


http://127.0.0.1:8000/miapp

Ahora tienes una copia de la aplicacin welcome.


Para editar una aplicacin, haz clic en el botn editar para la aplicacin recin creada.
La pgina para editar te muestra lo que contiene la aplicacin. Cada aplicacin de web2py
consiste de ciertos archivos, que en su mayora caen en las siguientes categoras:

modelos: describen la representacin de los datos.

controladores: describen el workflow o flujo de trabajo de la aplicacin.

vistas: describen la presentacin de los datos.

o
o

idiomas: describen como se debe traducir la presentacin de la aplicacin a otros


idiomas.
mdulos: mdulos de Python que pertenecen a la aplicacin.
archivos estticos:
JavaScript [js-w,js-b] , etc.

imgenes

estticas,

archivos

CSS

[css-w,css-o,css-school]

archivos

plugin: grupos de archivos diseados para trabajar en conjunto.

Todo est prolijamente organizado segn el patrn de diseo Modelo-Vista-Controlador. Cada


seccin en la pgina editar corresponde a una subcarpeta en la carpeta de la aplicacin.
Ntese que haciendo clic en los encabezados de seccin las secciones se contraen o
expanden. Los nombres de las carpetas bajo archivos estticos tambin tienen esta
propiedad.

Cada archivo listado en la seccin corresponde a un archivo con una ubicacin fsica en la
subcarpeta. Toda operacin realizada en un archivo a travs de la interfaz admin (crear,
editar, eliminar) se puede realizar directamente desde la shell utilizando nuestro editor
favorito.
La aplicacin contiene otros tipos de archivos (bases de datos, archivos de sesiones, archivos
de errores, etc.), pero no se listan en la pgina editar porque no son creadas o modificadas por
el administrador; esos archivos son creados y modificados por la aplicacin misma.
Los controladores contienen la lgica y flujo de trabajo de la aplicacin. Cada URL se asocia a
una llamada a una de las funciones en los controladores (acciones). Hay dos controladores
por defecto: "appadmin.py" y "default.py". appadmin provee de una interfaz administrativa
para la base de datos; por ahora no la usaremos. "default.py es el controlador que debes
editar, que es el que se va a llamar por defecto cuando no se especifique el controlador en la
URL. Modifica la funcin "index" de esta forma:
def index():
return "Hola desde MiApp"

As es como se ve el editor web:

Gurdalo y regresa a la pgina de editar. Haz clic en el link de index para visitar la nueva
pgina creada.
Cuando visitas la URL
http://127.0.0.1:8000/myapp/default/index

se llama a la accin index en el controlador por defecto (default). Este devuelve la cadena que
el navegador va a mostrarnos. Debera verse algo como esto:

Ahora, modifica la funcin "index" como sigue:


def index():
return dict(mensaje="Hola desde MiApp")

Tambin desde la pgina editar, modifica la vista "default/index.html" (el archivo de vista
asociado a la accin) y reemplaza completamente los contenidos de ese archivo con lo
siguiente:
<html>
<head></head>
<body>
<h1>{{=mensaje}}</h1>
</body>
</html>

Ahora la accin devuelve un diccionario que define un mensaje . Cuando una accin devuelve
un diccionario, web2py busca una vista con el nombre
[controller]/[function].[extension]

y la ejecuta. Aqu [extension] es la extensin de la solicitud. Si no se especifica la extensin,


se toma "html" por defecto, que es la extensin que utilizaremos para este caso. Con estas
especificaciones, la vista es un archivo con HTML que incrusta o embebe cdigo Python
utilizando etiquetas especiales con {{ }}. En particular, en el ejemplo, el {{=mensaje}} ordena
a web2py que reemplace el cdigo entre etiquetas con el valor de message devuelto por la
accin. Ntese que mensaje aqu no es una palabra especial (keyword) sino que se defini en
la accin. Hasta aqu no hemos utilizado keyword de web2py.

Si web2py no encuentra la vista solicitada, utiliza una vista llamada "generic.html" que viene
con cada aplicacin.

Si se especifica una extensin que no sea "html" (por ejemplo "json"), y el archivo de la
vista "[controlador]/[funcin].json" no se encuentra, web2py busca la vista genrica
"generic.json". web2py viene con generic.html, generic.json, generic.jsonp, generic.xml,
generic.rss, generic.ics (para Mac Mail Calendar), generic.map (para incrustar Google
Maps), y generic.pdf (basado en fpdf). Estas vistas genricas se pueden modificar para
cada aplicacin individualmente, y se pueden agregar vistas adicionales fcilmente.
Las vistas genricas son herramientas de desarrollo. En produccin cada accin debera
tener su propia vista. De hecho, por defecto, las vistas genricas slo estn habilitadas
para localhost.
Tambin puedes especificar una vista con response.view = 'default/something.html'
Consulta ms detalles sobre este tema en el Captulo 10.
Si regresas a EDITAR y haces clic en index, vers la nueva pgina HTML:

La barra de herramientas para depuracin


Con fines de depuracin es posible aadir
{{=response.toolbar()}}

al cdigo de la vista y te mostrar algunos datos tiles, incluyendo los objetos request,
response y session, y una lista de todas las consultas a la base de datos cronometradas.

Contemos
Ahora vamos a agregar un contador a esta pgina que llevar la cuenta de cuntas veces el
mismo visitante muestra la pgina.
web2py automtica y transparentemente registra a los usuarios utilizando sesiones y cookie.
Para cada nuevo visitante, crea una sesin y le asigna un "session_id" nico. El objeto session
es un contenedor para variables que se almacenan del lado del servidor. La clave nica id es
enviada al navegador a travs de una cookie. Cuando el visitante solicita otra pgina desde la
misma aplicacin, el navegador enva la cookie de regreso, es recuperada por web2py, y se
reactiva la sesin correspondiente.
Para utilizar el session, modifica el controlador por defecto (default):
def index():
if not session.contador:
session.contador = 1
else:
session.contador += 1
return dict(mensaje="Hola desde MiApp", contador=session.contador)

Ten en cuenta que contador no es una palabra especial de web2py pero s lo es session . Le
pedimos a web2py que revise si existe una variable contador en session y, si no es as, que
cree una con un valor inicial de 1. Si el contador ya existe, le pedimos a web2py que lo
aumente en una unidad. Finalmente le pasamos el valor del contador a la vista.
Una forma ms compacta de declarar la misma funcin es:
def index():
session.contador = (session.contador or 0) + 1
return dict(mensaje="Hola desde MiApp", contador=session.contador)

Ahora modifica la vista para agregar una lnea que muestre el valor del contador:
<html>
<head></head>
<body>
<h1>{{=mensaje}}</h1>

<h2>Cantidad de visitas: {{=contador}}</h2>


</body>
</html>

Cuando visitas la pgina index nuevamente (y otra vez), deberas obtener la siguiente pgina
HTML:

El contador est asociado a cada visitante, y se incrementa cada vez que el visitante vuelve a
cargar la pgina. Cada visitante ve un contador distinto.

Di mi nombre
Ahora crea dos pginas (primera y segunda), donde la primera pgina crea un formulario, le
pide el nombre al visitante, y redirige a la segunda pgina, que crea un mensaje de bienvenida
personalizado.

Escribe las acciones correspondientes en el controlador default:


def primera():
return dict()

def segunda():
return dict()

Luego crea una vista "default/primera.html" para la primera accin, e ingresa:

{{extend 'layout.html'}}
<h1>Cul es tu nombre?</h1>
<form action="segunda">
<input name="nombre_del_visitante" />
<input type="submit" />
</form>

Finalmente, crea una vista "default/segunda.html" para la segunda accin:


{{extend 'layout.html'}}
<h1>Hello {{=request.vars.nombre_del_visitante}}</h1>

En ambas vistas hemos extendido el "layout.html" bsico que viene con web2py. La vista
layout (plantilla general) mantiene la apariencia/estilo entre dos pginas coherente. El archivo
de layout se puede modificar y reemplazar fcilmente, ya que principalmente contiene cdigo
HTML.
Si ahora visitas la primera pgina, e ingresa tu nombre:

y aceptas el formulario, recibirs un mensaje de bienvenida:

Postback
El mecanismo para envo de formularios que hemos utilizado antes es muy comn, pero no es
una buena prctica de programacin. Todos los datos ingresados deberan validarse y, en el
ejemplo anterior, la tarea de validacin recaera en la segunda accin. De esa forma, la accin
que realiza la validacin es distinta a la que ha generado el formulario. Esto tiende a la
redundancia en el cdigo.
Un patrn ms apropiado para el envo de formularios es enviarlos a la misma accin que los
gener, en nuestro caso "primera". La accin "primera" debera recibir las variables,
procesarlas, almacenarlas del lado del servidor, y redirigir al visitante a la pgina "segunda",
que recupera las variables. Este mecanismo se denomina "postback".

Modifica el controlador default para implementar el auto-envo:


def primera():
if request.vars.nombre_del_visitante:
session.visitor_name = request.vars.nombre_del_visitante
redirect(URL('segunda'))
return dict()

def segunda():
return dict()

Y ahora modifica la vista "default/primera.html":


{{extend 'layout.html'}}
Cul es tu nombre?
<form>
<input name="nombre_del_visitante" />
<input type="submit" />
</form>

y la vista "default/segunda.html" necesita recuperar la informacin de session en lugar de


"request.vars":
{{extend 'layout.html'}}
<h1>Hola {{=session.visitor_name or "annimo"}}</h1>

Desde el punto de vista del visitante, el auto-envo se comporta exactamente igual que con la
implementacin anterior. No hemos aadido validacin todava, pero ahora est claro que la
validacin debera realizarla la primera accin.

Esta estrategia es mejor adems porque el nombre del visitante se mantiene en la sesin, y
permanece accesible en toda accin o vista de la aplicacin sin que sea necesario pasarlo
explcitamente.
Ten en cuenta que si la accin "segunda" se llamara antes de establecer el nombre del
visitante, mostrar "Hola annimo" porque session.visitor_name devuelve None . Como
alternativa podramos haber agregado el siguiente cdigo en el controlador (en la funcin
"segunda"):
if not request.function=='primera' and not session.visitor_name:
redirect(URL('primera'))

Este es un mecanismo ad hoc que puedes utilizar para un mayor control de la autorizacin en
los controladores, aunque puedes consultar un mtodo ms eficiente en el Captulo 9.
Con web2py podemos avanzar aun ms y pedirle que genere los formularios por nosotros,
incluyendo la validacin. web2py provee de ayudantes (FORM, INPUT, TEXTAREA, y
SELECT/OPTION) con nombres equivalentes a las etiquetas de HTML. Estos ayudantes
pueden utilizarse para crear formularios tanto en el controlador como en la vista.
Por ejemplo, aqu se muestra una posible forma de reescribir la primera accin:
def primera():
formulario = FORM(INPUT(_name='nombre_del_visitante', requires=IS_NOT_EMPTY()),
INPUT(_type='submit'))
if formulario.process().accepted:
session.nombre_del_visitante = formulario.vars.nombre_del_visitante
redirect(URL('segunda'))
return dict(formulario=formulario)

donde decimos que la etiqueta FORM contiene dos etiquetas INPUT. Los atributos de las
etiquetas se especifican con pares nombre/argumento (named arguments) que comienzan con
subguin ("_"). El argumento requires no es un atributo de etiqueta (porque no comienza con
subguin) pero establece un validador para el valor de nombre_del_visitante.
Aqu tenemos una forma todava mejor de crear el mismo formulario:
def primera():
formulario = SQLFORM.factory(Field('nombre_del_visitante',

label='Cul es tu nombre?',
requires=IS_NOT_EMPTY()))
if formulario.process().accepted:
session.nombre_del_visitante = form.vars.nombre_del_visitante
redirect(URL('segunda'))
return dict(formulario=formulario)

El objeto formulario puede serializarse fcilmente en HTML al incrustarlo en la vista


"default/primera.html".
{{extend 'layout.html'}}
{{=formulario}}

El mtodo formulario.process() aplica los validadores y devuelve el formulario en s. La


variable formulario.accepted se establece como True si el formulario se proces y pas la
validacin. Si el formulario auto-enviado pasa la validacin, almacena las variables en la
sesin y redirige como antes. Si el formulario no pasa la validacin, se insertan mensajes de
error en l y se muestran al usuario, como se muestra a continuacin:

En la prxima seccin vamos a mostrar como los formularios


automticamente desde el modelo.

pueden crearse

En todos los ejemplos hemos usado la sesin para pasar el nombre del usuario entre la
primera y la segunda accin. Podramos haber usado un mecanismo diferente pasando los
datos como parte del URL de redireccin:
def primera():
formulario = SQLFORM.factory(Field('nombre_del_visitante', requires=IS_NOT_EMPTY()))
if formulario.process().accepted:
nombre = form.vars.nombre_del_visitante

redirect(URL('segunda', vars=dict(nombre=nombre)))
return dict(formulario=formulario)

def segunda():
nombre = request.vars.nombre or redirect(URL('primera'))
return dict(nombre=nombre)

Ten en cuenta que en general no es buena idea pasar datos entre acciones por medio de los
URL. Esto hace ms difcil la proteccin de los datos. Es ms seguro almacenar los datos en
la sesin.

Internacionalizacin
Probablemente tu cdigo incluya parmetros fijos (hardcoded), como la cadena "Cul es tu
nombre?". Deberas poder personalizar esas cadenas sin tener que editar el cdigo fuente y
en especial agregar traducciones en diferentes idiomas. De esa forma, si un visitante tuviera
como preferencia el idioma italiano en su navegador, web2py usara la traduccin al italiano
para las cadenas, si estuvieran disponibles. Esta caracterstica de web2py se
llama internationalization y se describe con ms detalle en el prximo captulo.
Aqu solo nos interesa aclarar que para poder usar esta funcionalidad debes marcar las
cadenas que se deben traducir. Esto se hace envolviendo las cadenas entre comillas como por
ejemplo
"Cul es tu nombre?"

con el operador T :
T("Cul es tu nombre?")

Adems puedes marcar para traduccin cadenas fijas en las vistas. Por ejemplo
<h1>Cul es tu nombre?</h1>

se convierte en
<h1>{{=T("Cul es tu nombre?")}}</h1>

Es una buena prctica el hacerlo para toda cadena en el cdigo (etiquetas de campos,
mensajes emergentes, etc.) salvo para los nombres de tablas o campos de la base de datos.

Una vez que identifiquemos y marquemos las cadenas para traduccin, web2py se encargara
de prcticamente todo lo dems. Adems, la interfaz administrativa provee de una pgina
donde puedes realizar las traducciones de cada cadena en el idioma para el que quieras dar
soporte.

web2py incluye un potente motor para pluralizacin que se describe en el prximo


captulo. Se integra tanto al motor de internacionalizacin como al conversor para
markmin.

Un blog de imgenes
Aqu, como nuevo ejemplo, queremos crear una aplicacin web que permita al administrador
publicar imgenes y asignarles un nombre, y que permita a los usuarios del sitio web ver las
imgenes rotuladas y enviar comentarios (publicaciones).
Como antes, desde la pgina site en admin, crea una nueva aplicacin llamada imagenes , y
navega hasta la pgina de "editar":

Comenzamos creando un modelo, una representacin de los datos permanentes en la


aplicacin (las imgenes a subir, sus nombres y los comentarios). Primero, necesitas
crear/modificar un archivo de modelo que, por falta de imaginacin, llamaremos "db.py".
Suponemos que el cdigo a continuacin reemplazar todo cdigo existente en "db.py". Los
modelos y los controladores deben .py como extensin porque son cdigo fuente de Python.
Si la extensin no se provee, web2py la agrega automticamente. Las vistas en cambio
tienen .html como extensin ya que principalmente contienen cdigo HTML.
Modifica el archivo "db.py" haciendo clic en el botn "editar" correspondiente:

e ingresa lo siguiente:
db = DAL("sqlite://storage.sqlite")

db.define_table('imagen',
Field('titulo', unique=True),
Field('archivo', 'upload'),
format = '%(titulo)s')

db.define_table('publicacion',
Field('imagen_id', 'reference imagen'),
Field('autor'),
Field('email'),
Field('cuerpo', 'text'))

db.imagen.titulo.requires = IS_NOT_IN_DB(db, db.imagen.titulo)


db.publicacion.imagen_id.requires = IS_IN_DB(db, db.imagen.id, '%(titulo)s')
db.publicacion.autor.requires = IS_NOT_EMPTY()
db.publicacion.email.requires = IS_EMAIL()
db.publicacion.cuerpo.requires = IS_NOT_EMPTY()

db.publicacion.imagen_id.writable = db.publicacion.imagen_id.readable = False

Analicemos esto lnea a lnea.


La lnea 1 define una variable global llamada db que representa la conexin de la base de
datos. En este caso es una conexin a una base de datos SQLite almacenada en el archivo
"applications/imagenes/databases/storage.sqlite". Si se usa SQLite y el archivo de la base de
datos no existe, se crea uno nuevo. Puedes cambiar el nombre del archivo, as como tambin
el nombre de la variable global db , pero es conveniente que se les d el mismo nombre, para
que sea ms fcil recordarlos.
Las lneas 3-6 definen una tabla "imagen". define_table es un mtodo del objeto db . El
primer argumento "imagen", es el nombre de la tabla que estamos definiendo. Los otros
argumentos son los campos que pertenecen a la tabla. Esta tabla tiene un campo denominado
"titulo", otro llamado "archivo", y un campo llamado "id" que sirve como clave primaria ("id" no
se declara explcitamente porque todas las tablas tienen un campo id por defecto). El campo
"titulo" es una cadena, y el campo "archivo" es de tipo "upload". "upload" es un tipo de campo
especial usado por la Capa de Abstraccin de Datos (DAL) de web2py para almacenar los
nombres de los archivos subidos. web2py sabe como subir archivos (por medio de streaming
si son grandes), cambiarles el nombre por seguridad, y almacenarlos.
Cuando se define una tabla, web2py realiza una de muchas acciones posibles:
o

Si la tabla no existe, la tabla es creada;

Si la tabla existe y no se corresponde con la definicin, la tabla se modifica


apropiadamente, y si un campo tiene un tipo distinto, web2py intenta la conversin de sus
contenidos.

Si la tabla existe y se corresponde con la definicin, web2py no realiza ninguna accin.

Este comportamiento se llama "migracin" (migration). En web2py las migraciones son


automticas, pero se pueden deshabilitar por tabla pasando migrate=False como ltimo
argumento de define_table .

La lnea 6 define una cadena de formato para la tabla. Esto determina cmo un registro
debera representarse como cadena. Ntese que el argumento format tambin puede ser una
funcin que toma un registro y devuelve una cadena. Por ejemplo:
format=lambda registro: registro.titulo

Las lneas 8-12 definen otra tabla llamada "publicacion". Una publicacin tiene un "autor", un
"email" (vamos a guardar la direccin de correo electrnico del autor de la publicacin), un
"cuerpo" de tipo "text" (queremos utilizarlo para guardar el texto en s publicado por el autor), y
un campo "imagen_id" de tipo reference que apunta a db.imagen por medio del campo "id".
En la lnea 14, db.imagen.titulo representa el campo "titulo" de la tabla "imagen". El
atributo requires te permite configurar requerimientos/restricciones que se controlarn por
medio de los formularios de web2py. Aqu requerimos que "titulo" no se repita:
IS_NOT_IN_DB(db, db.imagen.titulo)

Ten en cuenta que esto es opcional porque se configura automticamente siempre que se
especifique Field('titulo', unique=True) .
Los objetos que representan estas restricciones se llaman validadores (validators). Se pueden
agrupar mltiples validadores en una lista. Los validadores se ejecutan en el orden en que se
especifican. IS_NOT_IN_DB(a, b) es un validador especial que comprueba que el valor de un
campo b para un nuevo registro no exista previamente en a .
La lnea 15 requiere que el campo "imagen_id" de la tabla "publicacion" est en db.imagen.id .
En lo que respecta a la base de datos, ya lo habamos declarado al definir la tabla
"publicacion". Ahora estamos diciendo explcitamente al modelo que esta condicin debera
ser controlada por web2py tambin en el nivel del procesamiento de los formularios cuando se
publica un comentario, para que los datos invlidos no se propaguen desde los formularios de
ingreso a la base de datos. Adems requerimos que la "imagen_id" se represente por el
"titulo", '%(titulo)s' , del registro correspondiente.
La lnea 20 indica que el campo "imagen_id" de la tabla "publicacion" no debera mostrarse en
formularios, writable=False y tampoco en formularios de slo-lectura, readable=False .
El significado de los validadores en las lneas 17-18 debera de ser obvio.
Ten en cuenta que el validador
db.publicacion.imagen_id.requires = IS_IN_DB(db, db.imagen.id, '%(titulo)s')

se puede omitir (y se configurara automticamente) si especificramos un formato para una


tabla referenciada:
db.define_table('imagen', ..., format='%(titulo)s')

donde el formato puede ser una cadena o una funcin que toma un registro y devuelve una
cadena.
Una vez que el modelo se ha definido, si no hay errores, web2py crea una interfaz de la
aplicacin para administrar la base de datos. Puedes acceder a ella a travs del link
"administracin de la base de datos" en la pgina "editar" o directamente:
http://127.0.0.1:8000/imagenes/appadmin

Aqu se muestra una captura de la interfaz appadmin:

Esta interfaz se escribe en el controlador llamado "addpadmin.py" y la vista "appadmin.html"


correspondiente. En adelante, nos referiremos a esta interfaz como "appadmin.py". Esta
interfaz permite al administrador insertar nuevos registros de la base de datos, editar y eliminar
registros existentes, examinar las tablas y hacer consultas tipo join en la base de datos.
La primera vez que se accede a appadmin, se ejecuta el modelo y se crean las tablas. La
DAL de web2py traduce el cdigo Python en comandos SQL especficos del motor de base de
datos implementado (en este ejemplo SQLite). Puedes ver el SQL generado desde la pgina

"editar" haciendo clic en el link "sql.log" debajo de "modelos". Ten en cuenta que el link no est
disponible si no se crean las tablas.

Si editas el modelo y accedes a appadmin de nuevo, web2py generar SQL para alterar las
tablas existentes. El SQL generado se registra en "sql.log".
Regresa a appadmin y prueba insertando un nuevo registro:

web2py ha traducido el campo tipo "upload" db.imagen.archivo en un formulario para subir el


archivo. Cuando el formulario se acepta y se sube una imagen, el archivo se cambia de
nombre por seguridad conservando la extensin, se guarda con el nuevo nombre en la carpeta
"uploads" de la aplicacin, y se almacena el nuevo nombre el campo db.imagen.archivo . Este
mecanismo est diseado para prevenir los ataques de tipo directory traversal.

Ten en cuenta que cada tipo de campo es procesado por un "widget". Los widget por defecto
se pueden reemplazar (override).
Cuando haces clic en un nombre de tabla en appadmin, web2py realiza un select de todos los
registros de la tabla elegida, encontrados por la consulta a la base de datos o query
db.imagen.id > 0

y convierte el resultado (render).

Puedes seleccionar un conjunto distinto de registros editando la consulta de DAL y


presionando [Enviar].
Para modificar o eliminar un solo registro, haz clic en su nmero id.

Por causa del validador IS_IN_DB , el campo de referencia "imagen_id" se convierte en un


men desplegable (drop-down). Los tems en el men son claves ( db.imagen.id ), pero se
representan con el campo db.imagen.titulo , como se especific al crear el validador.
Los validadores son objetos muy potentes que saben como representar campos, filtrar sus
valores, generar errores, y dar formato a los datos extrados del campo.
La siguiente figura muestra qu pasa cuando se acepta un formulario que no pasa la
validacin:

Los mismos formularios que se generan automticamente en appadmin pueden tambin


generarse en forma programtica a travs del ayudante SQLFORM e embebidos en
aplicaciones del usuario. Estos formularios son aptos para CSS (CSS-friendly), y se pueden
personalizar.

Cada aplicacin tiene su propio appadmin; por lo tanto, el mismo appadmin puede
modificarse sin que se afecten otras aplicaciones.
Hasta aqu, la aplicacin sabe cmo almacenar la informacin, y hemos visto cmo acceder a
la base de datos a travs de appadmin. El acceso a appadmin est restringido al
administrador, y no est pensado como una interfaz web de produccin para la aplicacin; De
ah la prxima parte de este tutorial paso a paso. Especficamente queremos crear:
o

Una pgina "index" que liste todas las imgenes disponibles ordenadas por ttulo y link
a pginas con detalles para las imgenes.

Una pgina "mostrar/[id]" que muestre al visitante la imagen solicitada y le permita ver
y publicar comentarios.

Una accin "download/[nombre]" para la descarga de las imgenes subidas.

Esto se muestra esquemticamente aqu:

Regresa a la pgina "editar" y modifica el controlador "default.py", reemplazando sus


contenidos con lo que sigue:
def index():
imagenes = db().select(db.imagen.ALL, orderby=db.imagen.titulo)
return dict(imagenes=imagenes)

Esta accin devuelve un diccionario. Las claves de los tems en el diccionario se interpretan
como variables pasadas a la vista asociada a la accin. Durante el desarrollo, si no hay una
vista, la accin es convertida (render) por la vista "generic.html" que se provee con cada
aplicacin de web2py.
La accin de index realiza una consulta select de todos los campos ( db.imagen.ALL ) de la
tabla imagen, ordenados por db.imagen.titulo . El resultado del select es un objeto Rows que
contiene los registros. Se asigna a una variable local llamada imagenes devuelta por la accin
a la vista. imagenes es iterable y sus elementos son los registros consultados. Para cada
registro

(row)

las

columnas

se

pueden

examinar

['titulo'] o imagenes[0].titulo con igual resultado.

como

diccionarios: imagenes[0]

Si no escribes una vista, el diccionario es convertido por "views/generic.html" y una llamada a


la accin index se vera de esta forma:

No has creado una vista por esta accin todava, as que web2py convierte el set de registros
en un formulario tabular simple.
Ahora crea una vista para la accin index. Regresa a admin, edita "default/index.html" y
reemplaza su contenido con lo que sigue:
{{extend 'layout.html'}}
<h1>Imgenes registradas</h1>
<ul>

{{for imagen in imagenes:}}


{{=LI(A(imagen.titulo, _href=URL("mostrar", args=imagen.id)))}}
{{pass}}
</ul>

Lo primero a tener en cuenta es que una vista es HTML puro con etiquetas {{...}} especiales.
El cdigo incrustado en {{...}} es cdigo Python puro con una salvedad: la indentacin o
espaciado no es relevante. Los bloques de cdigo comienzan con lneas que terminan en dos
puntos (:) y terminan en lneas que comienzan con la palabra clave pass . En algunos casos el
final de un bloque es obvio teniendo en cuenta el contexto y no es necesario el uso de pass .
Las lneas 5-7 recorren los registros de las imgenes y para cada registro muestran:
LI(A(imagen.titulo, _href=URL('mostrar', args=imagen.id))

Se trata de una etiqueta <li>...</li> que contiene una etiqueta <a href="...">...</a> que
contiene el campo imagen.titulo . El valor del hipervnculo o hypertext reference (atributo href)
es:
URL('mostrar', args=imagen.id)

Es decir, el URL en el mbito de la misma aplicacin y controlador que la solicitud (request)


actual que llama a una funcin llamada "mostrar", pasndole un argumento
nico args=imagen.id a esa funcin. LI , A , etc. son ayudantes de web2py que estn
asociados a las etiquetas HTML correspondientes. Sus argumentos sin nombre se interpretan
como objetos a serializar e insertar en el HTML incluido en la etiqueta. Los argumentos por
nombre que comienzan con subguin (por ejemplo _href ) son interpretados como atributos de
la etiqueta sin el subguin. Por ejemplo _href es el atributo href , _class es el atributo class ,
etc.
Por ejemplo, el siguiente comando:
{{=LI(A('algo', _href=URL('mostrar', args=123))}}

se convierte en:
<li><a href="/imagenes/default/mostrar/123">algo</a></li>

Algunos ayudantes ( INPUT , TEXTAREA , OPTION y SELECT ) tambin aceptan algunos


atributos especiales que no comienzan con subguin ( value , y requires ). Estos parmetros
son importantes para la creacin de formularios y se tratarn ms adelante.

Vuelve a la pgina "editar". Ahora nos indica que "default.py" expone "index". Haciendo clic en
"index", puedes visitar la nueva pgina creada:
http://127.0.0.1:8000/imagenes/default/index

que se ve as:

Si haces clic en el link del nombre de la imagen, el navegador abre la direccin:


http://127.0.0.1:8000/imagenes/default/mostrar/1

y esto resulta en un error, ya que todava no has creado una accin llamada "mostrar" en el
controlador "default.py".

Editemos el controlador "default.py" y reemplazando su contenido con:


def index():
imagenes = db().select(db.imagen.ALL, orderby=db.imagenes.titulo)
return dict(imagenes=imagenes)

def mostrar():
imagen = db.imagen(request.args(0, cast=int)) or redirect(URL('index'))
db.comentario.imagen_id.default = imagen.id
formulario = SQLFORM(db.comentario)
if formulario.process().accepted:
response.flash = 'tu comentario se ha publicado'
comentarios = db(db.comentario.imagen_id==image.id).select()
return dict(imagen=imagen, comentarios=comentarios, formulario=formulario)

def download():
return response.download(request, db)

El controlador contiene dos acciones: "mostrar" y "download". La accin "mostrar" selecciona


la imagen con el id ledo (parsed) de request args y todos los comentarios asociados a la
imagen. a continuacin, "mostrar" pasa todo a la vista "default/mostrar.html".
El id de la imagen en la referencia de:
URL('mostrar', args=imagen.id)

en "default/index.html", se puede consultar como:


request.args(0,cast=int)

desde la accin "mostrar". El argumento cast=int es opcional pero muy importante. Intenta
convertir (cast) la cadena pasada en PATH_INFO a un int. En caso de fallar genera la
excepcin apropiada en lugar de generar un ticket. Tambin se puede hacer una redireccin
en caso de un fallo al convertir el dato:
request.args(0, cast=int, otherwise=URL('error'))

Adems, db.imagen(...) es un atajo para


db(db.imagen.id==...).select().first()

La accin "download" espera un nombre de archivo en request.args(0) , arma la ruta a la


ubicacin donde se supone que se ha almacenado el archivo, y lo devuelve al cliente. Si el
archivo es demasiado grande, lo transfiere por medio de un stream sin sobrecargar la memoria
(overhead).
Ten en cuenta los siguientes comandos:
o

La lnea 7 crea un formulario de insercin


tabla db.publicacion utilizando slo los campos especificados.

SQLFORM

para

la

La lnea 8 configura el valor del campo de referencia, que no es parte del formulario de
ingreso de datos porque no est en la lista de campos especificados arriba.

La lnea 9 procesa el formulario enviado (las variables del formulario estn


en request.vars ) en el contexto de la sesin actual (la sesin se usa para prevenir envos
duplicados y para facilitar la navegacin). Si las variables del formulario se validan, el
nuevo comentario se inserta en la tabla db.publicacion ; de lo contrario el formulario se
modifica para incluir los mensajes de error (por ejemplo, si el email del autor no es
vlido). Todo esto lo hace la lnea 9!

La lnea 10 se ejecuta nicamente cuando el formulario se acepta, luego de que el


registro se inserte en la base de datos. response.flash es una variable que se muestra en
las vistas y es utilizada para notificar al visitante cuando se detecta un evento en la
aplicacin.

La lnea 11 selecciona todos los comentarios que refieren o apuntan a la imagen


actual.

La accin "download" (descargar) ya est definida en el controlador "default.py" de la


aplicacin de andamiaje.
La accin "download" no devuelve un diccionario, por lo que no necesita una vista. La accin
"mostrar", sin embargo, debera tener una vista, entonces regresa al admin y crea una nueva
vista llamada "default/mostrar.html"
Modifica este archivo y reemplaza su contenido con lo siguiente:
{{extend 'layout.html'}}
<h1>Imagen: {{=imagen.titulo}}</h1>

<center>
<img width="200px"
src="{{=URL('download', args=imagen.archivo)}}" />
</center>
{{if len(comentarios):}}
<h2>Comentarios</h2><br /><p>
{{for publicacion in comentarios:}}
<p>{{=publicacion.autor}} dice <i>{{=publicacion.cuerpo}}</i></p>
{{pass}}</p>
{{else:}}
<h2>Todava no se han publicado comentarios</h2>
{{pass}}
<h2>Publica un comentario</h2>
{{=formulario}}

Esta vista muestra el imagen.archivo llamando a la accin "download" dentro de una


etiqueta <img .../> . Si hay comentarios, los recorre en un bucle y muestra cada uno.
As es como se ver para el visitante:

Cuando un visitante enva un comentario a travs de esta pgina, el comentario se almacena


en la base de datos y se agrega al final de la pgina.

Agregando la autenticacin
La API de web2py para el Control de Acceso Basado en Roles es bastante sofisticado, pero
por ahora vamos a limitarnos a restringir el acceso a la accin mostrar a los usuarios
autenticados, dejando una descripcin ms detallada para el captulo 9.

Para limitar el acceso a los usuarios autenticados, debemos completar tres pasos. En un
modelo, por ejemplo "db.py", debemos aadir:
from gluon.tools import Auth
auth = Auth(db)
auth.define_tables(username=True)

En nuestro controlador, tenemos que agregar una accin:


def user():
return dict(formulario=auth())

Esto es suficiente para habilitar el login (autenticacin), logout (cerrar sesin), etc. La plantilla
general del diseo (layout) adems mostrar opciones en las que requieran autenticacin en la
parte superior derecha del navegador.

Ahora podemos decorar las funciones que queremos restringir, por ejemplo:
@auth.requires_login()
def mostrar():
...

Todo intento de acceder a


http://127.0.0.1:8000/imagenes/default/show/[imagen_id]

requerir autenticacin. Si el usuario no se autentica (login), ser redirigido a


http://127.0.0.1:8000/imagenes/default/user/login

La funcin user adems expone, entre otras, las siguientes acciones:


http://127.0.0.1:8000/imagenes/default/user/logout
http://127.0.0.1:8000/imagenes/default/user/register
http://127.0.0.1:8000/imagenes/default/user/profile
http://127.0.0.1:8000/imagenes/default/user/change_password
http://127.0.0.1:8000/imagenes/default/user/request_reset_password
http://127.0.0.1:8000/imagenes/default/user/retrieve_username
http://127.0.0.1:8000/imagenes/default/user/retrieve_password
http://127.0.0.1:8000/imagenes/default/user/verify_email
http://127.0.0.1:8000/imagenes/default/user/impersonate
http://127.0.0.1:8000/imagenes/default/user/not_authorized

Ahora, un usuario nuevo debe registrarse para poder autenticarse, leer y publicar comentarios.

Tanto el objeto auth como la funcin user estn definidos por defecto en la aplicacin de
andamiaje. El objeto auth es altamente personalizable y puede manejar aspectos como
verificacin por email, confirmacin de registro, CAPTCHA, y mtodos alternativos de
autenticacin por medio de los plugin.

Agregando los grid


Podemos
aadir
mejoras
a
lo
realizado
hasta
aqu
usando
los
gadget SQLFORM.grid y SQLFORM.smartgrid para crear una interfaz de administracin para
nuestra aplicacin:
@auth.requires_membership('administrador')
def administrar():
grid = SQLFORM.smartgrid(db.imagen, linked_tables=['comentario'])
return dict(grid=grid)

con su "views/default/administrar.html" asociado


{{extend 'layout.html'}}
<h2>Interfaz de administracin</h2>
{{=grid}}

Por medio de appadmin crea un grupo "administrador" y agrega algunos miembros al grupo.
Ellos podrn acceder a la interfaz administrativa
http://127.0.0.1:8000/imagenes/default/manage

con funcionalidad para la navegacin y bsqueda:

y opciones para crear, actualizar y eliminar imgenes y sus comentarios:

Configurando la plantilla general del diseo


Puedes configurar la plantilla general por defecto editando "views/layout.html" pero adems
puedes configurarla sin editar el HTML. De hecho, la plantilla de estilo "static/base.css" est

documentada y descripta en detalle en el captulo 5. Puedes cambiar el color, las columnas, el


tamao, bordes y fondo sin editar el HTML. Si deseas modificar el men, el ttulo o el subttulo,
puedes hacerlo en cualquier archivo del modelo. La aplicacin de andamiaje, configura los
valores por defecto de estos parmetros en el archivo "models/menu.py":
response.title = request.application
response.subtitle = 'Modifcame!'
response.meta.author = 't'
response.meta.description = 'describe tu app'
response.meta.keywords = 'bla bla bla'
response.menu = [ [ 'Inicio', False, URL('index') ] ]

Una wiki simple


En esta seccin, armamos una wiki simple y desde cero usando nicamente las API de bajo
nivel (a diferencia del uso de la caracterstica de wiki incorporada de web2py que se detalla en
la prxima seccin). El visitante podr crear pginas, realizar bsquedas de pginas por ttulo
y editarlas. El visitante adems podr publicar comentarios (de la misma forma que en las
aplicaciones anteriores), y adems publicar documentos (adjuntos con las pginas) y
enlazarlos desde las pginas. Como convencin, adoptaremos la sintaxis markmin para la
sintaxis de nuestra wiki. Adems implementaremos una pgina de bsqueda con Ajax, una
fuente de RSS para las pginas, y un servicio para la bsqueda de pginas a travs de XMLRPC[xmlrpc] . El siguiente diagrama lista las acciones que necesitamos implementar y los enlaces
que queremos establecer entre ellos.

Comienza creando una nueva app de andamiaje, con nombre "miwiki".

El modelo debe contener tres tablas: pgina, comentario, y documento. Tanto comentario
como documento hacen referencia a pgina porque pertenecen a ella. Un documento contiene
un campo archivo de tipo upload como en la anterior aplicacin de imgenes.
Aqu se muestra el modelo completo:
db = DAL('sqlite://storage.sqlite')

from gluon.tools import *


auth = Auth(db)
auth.define_tables()
crud = Crud(db)

db.define_table('pagina',
Field('titulo'),
Field('cuerpo', 'text'),
Field('creada_en', 'datetime', default=request.now),
Field('creada_por', 'reference auth_user', default=auth.user_id),
format='%(titulo)s')

db.define_table('comentario',
Field('pagina_id', 'reference pagina'),
Field('cuerpo', 'text'),
Field('creado_en', 'datetime', default=request.now),
Field('creado_por', 'reference auth_user', default=auth.user_id))

db.define_table('documento',
Field('pagina_id', 'reference pagina'),
Field('nombre'),
Field('archivo', 'upload'),
Field('creado_en', 'datetime', default=request.now),

Field('creado_por', 'reference auth_user', default=auth.user_id),


format='%(name)s')

db.pagina.titulo.requires = IS_NOT_IN_DB(db, 'page.title')


db.pagina.cuerpo.requires = IS_NOT_EMPTY()
db.pagina.creada_por.readable = db.pagina.creada_por.writable = False
db.pagina.creada_en.readable = db.pagina.creada_en.writable = False

db.comentario.cuerpo.requires = IS_NOT_EMPTY()
db.comentario.pagina_id.readable = db.comentario.page_id.writable = False
db.comentario.creado_por.readable = db.comentario.creado_por.writable = False
db.comentario.creado_en.readable = db.comentario.creado_en.writable = False

db.documento.nombre.requires = IS_NOT_IN_DB(db, 'documento.nombre')


db.documento.pagina_id.readable = db.documento.pagina_id.writable = False
db.documento.creado_por.readable = db.documento.creado_por.writable = False
db.documento.creado_en.readable = db.documento.creado_en.writable = False

Modifica el controlador "default.py" y crea las siguientes acciones:


o

index: listar todas las pginas wiki

crear: publicar una pgina wiki nueva

mostrar: mostrar una pgina wiki, listar sus comentarios y agregar comentarios nuevos

editar: modificar una pgina existente

documentos: administrar los documentos adjuntos de una pgina

download: descargar un documento (como en el ejemplo de las imgenes)

buscar: mostrar un campo de bsqueda y, a travs de una llamada de Ajax, devolver


los ttulos a medida que el visitante escribe

callback: una funcin callback de Ajax. Devuelve el HTML que se embebe en la pgina
de bsqueda mientras el visitante escribe.

Aqu se muestra el controlador "default.py":


def index():
""" Este controlador devuelve un diccionario convertido
por la vista

Lista todas las pginas wiki


index().has_key('pages')
True
"""
paginas = db().select(db.pagina.id, db.pagina.titulo, orderby=db.pagina.titulo)
return dict(paginas=paginas)

@auth.requires_login()
def crear():
"""crea una nueva pgina wiki en blanco"""
formulario = SQLFORM(db.pagina).process(next=URL('index'))
return dict(formulario=formulario)

def mostrar():
"""muestra una pgina wiki"""
esta_pagina = db.pagina(request.args(0, cast=int)) or redirect(URL('index'))
db.comentario.pagina_id.default = esta_pagina.id
formulario = SQLFORM(db.comentario).process() if auth.user else None
comentariospagina = db(db.comentario.pagina_id==esta_pagina.id).select()
return dict(pagina=esta_pagina, comentarios=comentariospagina, formulario=formulario)

@auth.requires_login()
def editar():
"""editar una pgina existente"""
esta_pagina = db.pagina(request.args(0, cast=int)) or redirect(URL('index'))
formulario = SQLFORM(db.pagina, esta_pagina).process(
next = URL('mostrar', args=request.args))
return dict(formulario=formulario)

@auth.requires_login()
def documentos():
"""lista y edita los comentarios asociados a una pgina determinada"""
pagina= db.pagina(request.args(0, cast=int)) or redirect(URL('index'))
db.documento.pagina_id.default = pagina.id
db.documento.pagina_id.writable = False
grid = SQLFORM.grid(db.documento.pagina_id==pagina.id, args=[pagina.id])
return dict(pagina=pagina, grid=grid)

def user():
return dict(formulario=auth())

def download():
"""permite la descarga de documentos"""
return response.download(request, db)

def buscar():
"""una pgina de bsqueda de wikis via ajax"""
return dict(formulario=FORM(INPUT(_id='palabra',_name='palabra',
_onkeyup="ajax('callback', ['palabra'], 'target');")),
target_div=DIV(_id='target'))

def callback():
"""un callback de ajax que devuelve un <ul> de link a pginas wiki"""
consulta = db.pagina.titulo.contains(request.vars.palabra)
paginas = db(consulta).select(orderby=db.page.title)
direcciones = [A(p.titulo, _href=URL('mostrar',args=p.id)) for p in paginas]
return UL(*direcciones)

Las lneas 2-6 consisten en un comentario de la accin index. Las lneas 4-5 dentro de los
comentarios son interpretadas por Python como cdigo de doctest. Los tests se pueden
ejecutar a travs de la interfaz admin. En este caso el test verifica que la accin index corra sin
errores.
Las lneas 18, 27 y 35 tratan de recuperar el registro pagina con el id en request.args(0) .
Las lneas 13 y 20 definen y procesan formularios de creacin para una nueva pgina y un
nuevo comentario.
La lnea 28 define y procesa un formulario de modificacin para una pgina wiki.
La lnea 38 crea un objeto grid que permite visualizar, agregar y actualizar comentarios
asociados a una pgina.
Cierta magia ocurre en la lnea 51. Se configura el atributo onkeyup de la etiqueta "palabra".
Cada vez que el visitante deja de presionar una tecla, el cdigo JavaScript dentro del
atributo onkeyup se ejecuta, del lado del cliente. Este es el cdigo JavaScript:
ajax('callback', ['palabra'], 'target');
ajax es una funcin JavaScript definida en el archivo "web2py.js" que se incluye por defecto

en "layout.html". Toma tres parmetros: el URL de la accin que realiza el callback asncrono,
una lista de los IDs de variables a ser enviadas al callback (["palabra"]), y el ID donde la
respuesta se debe insertar ("target").
Tan pronto como escribas algo en el campo de bsqueda y dejes de presionar una tecla, el
cliente llama al servidor y enva el contenido del campo "palabra", y, cuando el servidor
responde, la respuesta se embebe en la misma pgina como HTML incluido en la etiqueta de
destino ('target').

La etiqueta 'target' es un DIV definido en la lnea 52. Podra haberse definido en la vista
tambin.
Aqu se muestra el cdigo para la vista "default/crear.html":
{{extend 'layout.html'}}
<h1>Crear una nueva pgina wiki</h1>
{{=formulario}}

Suponiendo que ya te has registrado y autenticado, si visitas la pgina crear, vers lo


siguiente:

Aqu se muestra el cdigo para la vista "default/index.html":


{{extend 'layout.html'}}
<h1>Pginas wiki disponibles</h1>
[ {{=A('buscar', _href=URL('buscar'))}} ]<br />
<ul>{{for pagina in paginas:}}

{{=LI(A(pagina.titulo, _href=URL('mostrar', args=pagina.id)))}}


{{pass}}</ul>
[ {{=A('crear pgina', _href=URL('crear'))}} ]

Esto crea la siguiente pgina:

Aqu se puede ver el cdigo para la vista "default/mostrar.html":

{{extend 'layout.html'}}
<h1>{{=pagina.titulo}}</h1>
[ {{=A('editar', _href=URL('editar', args=request.args))}}
| {{=A('documentos', _href=URL('documentos', args=request.args))}} ]<br />
{{=MARKMIN(pagina.cuerpo)}}
<h2>Comentarios</h2>
{{for comentario in comentarios:}}
<p>{{=db.auth_user[comentario.creado_por].first_name}} on {{=comentario.creado_en}}
dice <I>{{=comentario.cuerpo}}</i></p>
{{pass}}
<h2>Publicar un comentario</h2>
{{=formulario}}

Si deseas utilizar la sintaxis markdown en lugar de markmin:


from gluon.contrib.markdown import WIKI as MARKDOWN

y usa MARKDOWN en lugar del ayudante MARKMIN . Alternativamente, puedes elegir aceptar
HTML puro en lugar de la sintaxis markmin. En ese caso deberas reemplazar:
{{=MARKMIN(pagina.cuerpo)}}

con:
{{=XML(pagina.cuerpo)}}

(para que no se realice el "escapado" del XML, que es el comportamiento por defecto de
web2py por razones de seguridad).
Esto es mejor hacerlo con:
{{=XML(pagina.cuerpo, sanitize=True)}}

Al configurar sanitize=True , le dices a web2py que "escape" las etiquetas XML delicadas
como "<script>", y que de esa forma se puedan prevenir las vulnerabilidades de tipo XSS.
Ahora si, desde la pgina index, haces clic en el ttulo de una pgina, puedes ver la pgina
que has creado:

Aqu est el cdigo para la vista "default/edit.html":


{{extend 'layout.html'}}
<h1>Edicin de la pgina wiki</h1>
[ {{=A('mostrar', _href=URL('mostrar', args=request.args))}} ]<br />
{{=formulario}}

Genera una pgina que se ve prcticamente idntica a la de crear.


Aqu se muestra el cdigo de la vista "default/documentos.html":
{{extend 'layout.html'}}
<h1>Documentos para la pgina: {{=pagina.titulo}}</h1>
[ {{=A('mostrar', _href=URL('mostrar', args=request.args))}} ]<br />
<h2>Documentos</h2>
{{=grid}}

Si, desde la pgina "mostrar", haces clic en documentos, ahora puedes administrar los
documentos asociados a la pgina.

Por ltimo, aqu est el cdigo para la vista "default/buscar.html":


{{extend 'layout.html'}}
<h1>Buscar pginas wiki</h1>

[ {{=A('listar todo', _href=URL('index'))}}]<br />


{{=formulario}}<br />{{=target_div}}

que genera el siguiente formulario Ajax de bsqueda:

Adems puedes probar llamando a la accin callback directamente visitando, por ejemplo, el
siguiente URL:

http://127.0.0.1:8000/miwiki/default/callback?palabra=wiki

Si ahora examinas el cdigo fuente vers el HTML devuelto por el callback:


<ul><li><a href="/miwiki/default/mostrar/4">He creado una wiki</a></li></ul>

Es fcil generar una fuente de RSS para las pginas de la wiki utilizando web2py porque
incluye gluon.contrib.rss2 . Slo debes aadir la siguiente accin al controlador default:
def noticias():
"""genera una fuente de rss a partir de las pginas wiki"""
response.generic_patterns = ['.rss']
paginas = db().select(db.pagina.ALL, orderby=db.pagina.titulo)
return dict(
titulo = 'fuente rss de miwiki',
link = 'http://127.0.0.1:8000/miwiki/default/index',
description = 'noticias de miwiki',
creada_en = request.now,
elementos = [
dict(titulo = registro.titulo,
link = URL('mostrar', args=registro.id),
descripcion = MARKMIN(registro.cuerpo).xml(),
creada_en = registro.creada_en
) for registro in paginas])

y cuando visitas la pgina


http://127.0.0.1:8000/miwiki/default/news.rss

vers la fuente (la salida exacta depende del lector de fuentes rss). Observa cmo el dict se
convierte automticamente a RSS, gracias a la extensin en el URL.

web2py tambin incluye un intrprete (parser) de fuentes para leer fuentes de terceros.
Observa que la lnea:
response.generic_patterns = ['.rss']

le indica a web2py que debe usar vistas genricas (para nuestro ejemplo "views/generic.css")
cuando la terminacin del URL coincide con el patrn glob ".rss". Por defecto las vistas
genricas solo estn habilitadas para el desarrollo desde localhost.
Por ltimo, agreguemos un manejador de XML-RPC que permita la bsqueda de wiki en forma
programtica:

service = Service()

@service.xmlrpc
def buscar_por(palabra):
"""busca pginas que contengan la palabra para XML-RPC"""
return db(db.pagina.titulo.contains(palabra)).select().as_list()

def call():
"""expone todos los servicios registrados, incluyendo XML-RPC"""
return service()

Aqu, la accin de manejo (handler action) simplemente publica (via XML-RPC), las funciones
especificadas en la lista. En este caso, buscar_por no es una accin (porque toma un
argumento). Consulta a la base de datos con .select() y luego extrae los registros en forma de
lista con .as_list() y devuelve la lista.
Aqu hay un ejemplo de cmo se accede al manejador de XML-RPC desde un programa
externo en Python.
>>> import xmlrpclib
>>> servidor = xmlrpclib.ServerProxy(
'http://127.0.0.1:8000/mywiki/default/call/xmlrpc')
>>> for item in servidor.buscar_por('wiki'):
print(item['creada_en'], item['title'])

Se puede acceder al manejador desde distintos lenguajes de programacin que hablen XMLRPC, incluyendo C, C++, C# y Java.

Acerca de los formatos date, datetime y time


Existen tres distintas representaciones para cada uno de los tipos date , datetime y time :
o

la representacin de la base de datos

la representacin interna de web2py

la representacin como cadena en formularios y tablas

La representacin de la base de datos es una cuestin interna y no afecta al cdigo.


Internamente,
en
el
nivel
de
web2py,
se
almacenan
como
objetos datetime.date , datetime.datetime y datetime.time respectivamente y pueden ser
manipulados segn las clases mencionadas:
for pagina in db(db.pagina).select():
print pagina.title, pagina.day, pagina.month, pagina.year

Cuando las fechas se convierten a cadenas en los formularios son convertidas utilizando la
representacin ISO
%Y-%m-%d %H:%M:%S

de todas formas esta representacin est internacionalizada y puedes usar la pgina de


traduccin de la aplicacin administrativa para cambiar el formato por uno alternativo. Por
ejemplo:
%m/%b/%Y %H:%M:%S

Ten en cuenta que por defecto el idioma Ingls no se traduce porque web2py asume que
las aplicaciones ya vienen escritas en ese idioma. Si quieres que la internacionalizacin
funcione con el idioma Ingls debes crear el archivo de traduccin (utilizando admin) y
declarar que el lenguaje actual de la aplicacin es otro distinto del Ingls, por ejemplo:
T.current_languages = ['null']

La wiki incorporada de web2py


Ahora puedes olvidarte del cdigo que hemos creado en la seccin anterior (pero no de lo que
has aprendido sobre las API de web2py, slo el cdigo especfico de ese ejemplo), porque
vamos a presentar un ejemplo de la wiki incorporada de web2py.
En realidad, web2py incluye la funcionalidad de wiki con caractersticas como el soporte para
adjuntar archivos y recursos multimedia (media attachments), etiquetas y nubes de etiquetas,
permisologa de pginas, el uso de componentes (captulo 14) y el protocolo oembed [oembed].
Esta wiki se puede utilizar con cualquier aplicacin de web2py.

Ten en cuenta que la API de la wiki incorporada todava est considerada como
experimental y por lo tanto es posible que se le hagan modificaciones menores.

Aqu vamos a suponer que comenzaremos desde cero con un simple clon de la aplicacin
"welcome" que llamaremos "wikidemo". Modifica el controlador y reemplaza la accin "index"
con
def index(): return auth.wiki()

Hecho! Tienes una wiki completamente funcional. Hasta ahora no se han agregado pginas y
para poder hacerlo debes estar autenticado y ser miembro del grupo llamado "wiki_editor" o
del grupo "wiki_author" (autores de wiki). Si te has autenticado como administrador el grupo
"wiki_editor" se crear automticamente y sers agregado como miembro. La diferencia entre
editores y autores es que los editores pueden crear, modificar y borrar cualquier pgina,
mientras que los autores pueden crear y modificar pginas (con restricciones opcionales) y
pueden modificar o eliminar las pginas que han creado.
La

funcin auth.wiki() devuelve

un

diccionario

con

una

clave content que

es

automticamente detectada por la vista "default/index.html". Puedes crear tu propia vista para
esa accin:
{{extend 'layout.html'}}
{{=content}}

y agregar HTML adicional o el cdigo que necesites. No es obligatorio que la accin se llame
"index" para exponer la wiki. Puedes usar una accin con otro nombre.
Para probar la wiki, simplemente accede a la interfaz admin, y luego visita la pgina
http://127.0.0.1:8000/wikidemo/default/index

Luego elige un ttulo abreviado o slug (en el oficio grfico, el trmino del ingls slug es un
nombre corto que se usa para ttulos de artculos en produccin) y sers redirigido a una
pgina vaca donde puedes editar los contenidos usando la sintaxis de wiki markmin. Un
nuevo men llamado "[wiki]" te permitir crear, buscar y modificar las pginas. Las pginas
wiki tienen URL como el siguiente:
http://127.0.0.1:8000/wikidemo/default/index/[slug]

Las pginas de los servicios tienen nombres que comienzan con guin bajo:
http://127.0.0.1:8000/wikidemo/default/index/_create
http://127.0.0.1:8000/wikidemo/default/index/_search
http://127.0.0.1:8000/wikidemo/default/index/_could

http://127.0.0.1:8000/wikidemo/default/index/_recent
http://127.0.0.1:8000/wikidemo/default/index/_edit/...
http://127.0.0.1:8000/wikidemo/default/index/_editmedia/...
http://127.0.0.1:8000/wikidemo/default/index/_preview/...

Prueba a crear ms pginas como "index", "quienes-somos" y "contactenos" Luego prueba a


modificarlas
El mtodo wiki tiene la siguiente lista de parmetros o signature:
def wiki(self, slug=None, env=None, render='markmin',
manage_permissions=False, force_prefix='',
restrict_search=False, resolve=True,
extra=None, menugroups=None)

Acepta los siguientes argumentos:


o

render , que es por defecto 'markmin' pero que puede establecerse como 'html' .

Determina la sintaxis de la wiki. Daremos ms detalles de la sintaxis markmin ms


adelante. Si cambias este parmetro a HTML podras necesitar agregar un
editor WYSIWYGJavaScript como TinyMCE o NicEdit.
o

manage_permissions . Esta opcin tiene el valor False por defecto y slo reconocer la

permisologa para "wiki_editor" y "wiki_author". Si lo cambias a True , la pgina de


creacin y modificacin dar la opcin de especificar por nombres los grupos cuyos
miembros tienen permiso para leer y modificar la pgina. Se puede usar el grupo
"everybody" para otorgar permisos a todo usuario.
o

force_prefix . Si se especifica algo como '%(id)s' restringir la creacin de pginas de

los autores (no editores) con prefijos "[id del usuario]-[nombre de pgina]". El prefijo
puede contener el id ("%(id)s") o el nombre del usuario ("%(username)s") o cualquier otro
campo de la tabla auth_user, siempre y cuando la columna correspondiente contenga
una cadena capaz de pasar la validacin del URL.
o

restrict_search . Por defecto es False e implica que todo usuario autenticado puede

hacer bsquedas de pginas para toda la wiki (aunque no necesariamente tendr acceso
de lectura o escritura a toda pgina). Si se especifica True , los autores pueden buscar
nicamente entre sus propias pginas, los editores podrn buscar cualquier pgina, y el
resto de los usuarios no tendrn acceso a la bsqueda.

menu_groups . Si se establece como None (el valor por defecto), el men de

administracin de la wiki (para bsqueda, creacin, modificacin, etc.) estar siempre


disponible. Puedes especificar esta opcin como una lista de nombres de grupos para los
cuales ser visible el men, por ejemplo ['wiki_editor', 'wiki_author'] . Ten en cuenta que
incluso cuando el men se exponga a todos los usuarios, esto no implica que todo
usuario tenga acceso a todos los comandos del men, ya que estos permisos estn
regulados por el sistema de control de acceso.
El

mtodo wiki tiene

algunos

parmetros

adicionales

que

se

explicarn

ms

adelante: slug , env y extra .

Elementos bsicos de MARKMIN


La sintaxis MARKMIN te permite marcar un texto como negrita usando **negrita** , el texto
con letra itlica con ''itlica'' , y el texto con cdigo fuente se debe delimitar con comillas
invertidas (`) dobles. Para los ttulos se debe anteponer con un #, para las secciones ## y para
secciones menores ###. Usa el signo menos (-) como prefijo en listas sin orden de elementos
y un ms (+) como prefijo en listas ordenadas. Los URL se convierten automticamente en
link. He aqu un ejemplo de texto markmin:
# Este es un ttulo
## Este es un ttulo de seccin
### Este es un ttulo de una seccin menor

El texto puede tener **negrita**, ''itlica'', ``cdigo`` etc.


Puedes encontrar ms informacin en:

http://web2py.com

Puedes usar el parmetro extra de auth.wiki para pasar reglas adicionales de conversin al
ayudante MARKMIN. Encontrars ms informacin sobre la sintaxis MARKMIN en el captulo
5.
auth.wiki es ms potente que el ayudante MARKMIN por s solo, ya que, por ejemplo, tiene

soporte para oembed y componentes.


Puedes usar el parmetro env de auth.wiki para exponer funciones en tu wiki.

Por ejemplo:
auth.wiki(env=dict(unir=lambda a, b, c:"%s-%s-%s" % (a, b, c)))

Te permite usar el siguiente lenguaje de marcado o markup syntax:


@(join:1,2,3)

Esto llama a la funcin unir que se pas como parmetro en extra y especifica los
argumentos a, b, c=1, 2, 3 y se convertir como 1-2-3 .

El protocolo oembed
Puedes agregar(o copiar y pegar) cualquier URL en una pgina wiki y normalmente se
convertir en un link a ese URL. Hay algunas excepciones:
o

Si el URL tiene una extensin de imagen, el link se incrustar como imagen, <img/> .

Si el URL tiene una extensin de audio, el link se incrustar como audio


HTML5 <audio/> .

Si el URL tiene una extensin de MS Office o PDF, se embebe un Google Doc Viewer,
que muestra el contenido del documento (slo funciona para documentos pblicos).

Si el URL est vinculado a una pgina de YouTube, Vimeo o Flickr, web2py conecta
con el servicio correspondiente y consulta la forma apropiada en que se debe incrustar el
contenido. Esto se hace utilizando el protocolo oembed .

Esta es una lista completa de los formatos soportados:


Imagen (.PNG, .GIF, .JPG, .JPEG)
Audio (.WAV, .OGG, .MP3)
Video (.MOV, .MPE, .MP4, .MPG, .MPG2, .MPEG, .MPEG4, .MOVIE)

Los formatos soportados a travs del servicio de Google Doc Viewer:


Microsoft Excel (.XLS and .XLSX)
Microsoft PowerPoint 2007 / 2010 (.PPTX)
Apple Pages (.PAGES)
Adobe PDF (.PDF)

Adobe Illustrator (.AI)


Adobe Photoshop (.PSD)
Autodesk AutoCad (.DXF)
Scalable Vector Graphics (.SVG)
PostScript (.EPS, .PS)
TrueType (.TTF)
XML Paper Specification (.XPS)

Soportados por oembed:


flickr.com
youtube.com
hulu.com
vimeo.com
slideshare.net
qik.com
polleverywhere.com
wordpress.com
revision3.com
viddler.com

La

implementacin

pertenece

al

archivo

de

web2py gluon.contrib.autolinks ms

especficamente en la funcin exand_one . Puedes extender el soporte para oembed


registrando ms servicios. Esto se hace agregando una entrada a la lista EMBED_MAPS :
from gluon.contrib.autlinks import EMBED_MAPS
EMBED_MAPS.append((re.compile('http://vimeo.com/\S*'),
'http://vimeo.com/api/oembed.json'))

Creando hipervnculos para contenidos de la wiki


Si creas una pgina wiki con el ttulo "contactenos", puedes hacer referencia a ella como
@////contactenos

Aqu @//// reemplaza a


@/app/controlador/funcion/

per "app", "controlador" y "funcin" se omiten usando los valores por defecto.
En forma similar, puedes usar el men de la wiki para subir archivos multimedia (por ejemplo
una imagen) asociados a la pgina. La pgina "manage media" (administracin de archivos
multimedia) mostrar todos los archivos que se hayan subido y mostrar la notacin apropiada
con el hipervnculo al archivo multimedia. Si, por ejemplo, subes un archivo "prueba.jpg", con
el ttulo "playa", la notacin del hipervnculo ser algo como:
@////15/playa.jpg
@//// es el mismo prefijo que se describi anteriormente. 15 es el id del registro que

almacena el archivo multimedia. playa es el ttulo. .jpg es la extensin del archivo original.
Si cortas y pegas @////15/playa.jpg en la pgina wiki incrustars la imagen.
Ten en cuenta que los archivos multimedia estn asociados a las pginas y heredan sus
permisos de acceso.

Mens de la wiki
Si creas una pgina con el slug "wiki-menu" ser interpretado como la descripcin del men.
He aqu un ejemplo:
- Inicio > @////index
- Informacion > @////info
- web2py > http://www.web2py.com
- - Quines somos > @////quienes-somos
- - Contctenos > @////contactenos

Cada lnea es un tem del men. Para los mens anidados se usa el guin doble. El
signo > separa el ttulo del tem de men del link.
Ten en cuenta que el men se agrega al objeto response.menu , no lo reemplaza. Los tems
del men [wiki] que dan acceso a los servicios de la wiki se agregan automticamente.

Las funciones de servicios


Si por ejemplo, quieres usar la wiki para crear una barra lateral editable, puedes crear una
pgina con slug="sidebar" y luego embeberla en el layout.html con
{{=auth.wiki(slug='sidebar')}}

Ten en cuenta que la palabra "sidebar" aqu no tiene un significado especial. Toda pgina de
wiki se puede recuperar y embeber en cualquier instancia de tu cdigo fuente. Esto permite
entrelazar funcionalidades de la wiki con las funcionalidades comunes de las aplicaciones de
web2py.

Tambin debes tener en cuenta que auth.wiki('sidebar') es lo mismo


que auth.wiki(slug='sidebar') , ya que el argumento slug es el primero en la lista de
argumentos del mtodo. El primer comando provee de una sintaxis un tanto ms simple.
Adems puedes incrustar las funciones especiales de la wiki como la bsqueda por etiquetas:
{{=auth.wiki('_search')}}

o la nube de etiquetas:
{{=auth.wiki('_cloud')}}

Ampliacin de la funcionalidad auth.wiki


Cuando tu app con la wiki incorporada se torne ms complicada, quizs quieras personalizar
los registros de la base de datos para la wiki que son administrados por la interfaz Auth o
exponer formularios personalizados para las altas, bajas y modificaciones (ABM o CRUD). Por
ejemplo, podras querer personalizar la representacin de un registro de una tabla de la wiki o
agregar un nuevo validador de campo. Esto no es posible por defecto, ya que el modelo de la
wiki se define nicamente luego de que la interfaz wiki se inicie utilizando el mtodo
auth.wiki(). Para permitir el acceso a la configuracin especfica de la base de datos para la
wiki en el modelo de la app, debes agregar el siguiente comando a tu modelo (por ejemplo, en
db.py)
# Asegrate de que la funcin se llame luego de crear el objeto auth
# y antes de cualquier cambio en las tablas de la wiki
auth.wiki(resolve=False)

Al utilizra la lnea de arriba en tu modelo, podrs acceder a las tablas de la wiki (por
ejemplo, wiki_page ) para operaciones personalizadas en la base de datos.

Ten en cuenta que de todas formas debes usar auth.wik() en el controlador o vista para
poder exponer la interfaz wiki, ya que el parmetro resolve=False slo le indica al objeto
auth que debe configurar el modelo de la wiki sin realizar otra accin de configuracin
especfica.
Adems, al establecer resolve como False en el llamado al mtodo, se podr acceder a las
tablas y los registros de la wiki travs de la interfaz por defecto para la base de datos
en <app>/appadmin .
Otra personalizacin posible es la de agregar campos adicionales a los estndar de la wiki (de
igual forma que para la tabla auth_user , como se describe en el captulo 9). Esto se hace de
la siguiente forma:
# coloca este cdigo luego de la inicializacin del objeto auth
auth.settings.extra_fields["wiki_page"] = [Field("unblob", "blob"),]

La lnea de arriba agrega un campo blob a la tabla wiki_page . No hay necesidad de llamar
a auth.wiki(resolve=False) para esta opcin, a menos que se requiera el acceso al modelo de
la wiki para otras personalizaciones.

Componentes
Una de las funciones ms potentes del nuevo web2py consiste de la habilidad para embeber
una accin dentro de otra accin. A esta caracterstica la llamamos componentes.
Consideremos el siguiente modelo:
db.define_table('cosas',Field('nombre', requires=IS_NOT_EMPTY()))

y la siguiente accin:
@auth.requires_login()
def administrar_cosas():
return SQLFORM.grid(db.cosa)

Esta accin es especial porque devuelve un widget/ayudante y no un diccionario de objetos.


Ahora podemos incrustar la accin administrar_cosas en la vista, con
{{=LOAD('default','manage_things', ajax=True)}}

Esto permite que un visitante pueda interactuar con el componente a travs de Ajax sin
actualizar la pgina anfitrin que contiene el widget. La accin es llamada por medio de Ajax,
hereda el estilo de la pgina anfitrin y captura todos los envos de formularios y mensajes
emergentes para que sean manejados en la pgina actual. Como complemento de todo lo
anterior, el widget SQLFORM.grid usa direcciones URL firmadas digitalmente para restringir el
acceso. Se puede encontrar informacin ms detallada sobre componentes en el captulo 13.
Los componentes como el que se describe arriba se pueden incrustar en pginas wiki usando
la sintaxis MARKMIN:
@{component:default/administrar_cosas}

Esto sencillamente le indica a web2py que queremos incluir la accin "administrar_cosas"


definida en el controlador "default" a travs de Ajax.

La mayora de los usuarios podrn crear aplicaciones relativamente complejas


simplemente usando auth.wiki para crear pginas y mens, e incrustar componentes
personalizados en pginas wiki. Wiki puede ser pensado como un mecanismo para
permitir crear pginas a los miembros de un grupo determinado, pero tambin puede
entenderse como una metodologa modular para el desarrollo de aplicaciones.

Ms sobre admin
La interfaz administrativa provee de funcionalidad adicional que se tratar brevemente en esta
seccin.

Site
Esta pgina es la interfaz administrativa principal de web2py. A la izquierda tiene una lista de
todas las aplicaciones instaladas y a la derecha tiene algunos formularios especiales.
El primero de esos formularios muestra la versin de web2py y da la opcin de hacer
un upgrade si existe una nueva versin disponible. Por supuesto, antes de actualizar,
asegrate de hacer una copia de seguridad completa!
Luego hay dos formularios que permiten la creacin de una nueva aplicacin (en un solo paso
o usando un ayudante en lnea) especificando su nombre.
El formulario que sigue permite subir una aplicacin existente tanto desde una ubicacin local
del sistema como desde un URL remoto. Cuando subes una aplicacin, debes especificar su
nombre (el uso de distintos nombres te permite instalar mltiples copias de una misma

aplicacin). Puedes probar, por ejemplo, a subir la aplicacin de redes sociales Movuca
creada por Bruno Rocha:
https://github.com/rochacbruno/Movuca

o el CMS Instant Press creado por Martn Mulone:


http://code.google.com/p/instant-press/

o uno de los muchos ejemplos disponibles en:


http://web2py.com/appliances

Las apps de web2py se empaquetan como archivos .w2p , que son archivos tar
comprimidos con gzip. web2py utiliza la extensin .w2p en lugar de .tgz para evitar que el
navegador intente descomprimirlos al descargarlos. Se pueden descomprimir
manualmente con tar zxvf [nombredearchivo] aunque esto normalmente no es necesario.

Si la subida es exitosa, web2py muestra la suma de verificacin (checksum) del archivo


subido. Puedes utilizarla para verificar que el archivo no se da durante la subida. El nombre
InstantPress aparecer en la lista de aplicaciones instaladas.
Si corres la distribucin de web2py de cdigo fuente y tienes gitpython instalado (si es
necesario, puedes configurarlo con 'easy_install gitpython'), puedes instalar aplicaciones en
forma directa desde los repositorios git utilizando el URL con extensin .git del formulario
para subir aplicaciones. En ese caso tambin podrs usar la interfaz admin para aplicar los
cambios en el repositorio remoto, pero esa funcionalidad es experimental. Por ejemplo,
puedes instalar en forma local la aplicacin que muestra este libro en el sitio de web2py con el
URL:
https://github.com/mdipierro/web2py-book.git

Ese repositorio aloja la versin actual de este libro (que puede diferir de la versin estable
que ves en el sitio web). Las mejoras, reparaciones de fallos y correcciones son
bienvenidas y se pueden enviar como solicitud de cambios de git (pull request)

En funcin de cada aplicacin instalada puedes usar la pgina site para:


o

Acceder directamente a la aplicacin haciendo clic en su nombre.

Desinstalar la aplicacin

Ir a la pgina "acerca de" (ver abajo)

Ir a la pgina de "editar" (ver abajo)

Ir a la pgina de "errores" (ver abajo)

Eliminar archivos temporarios (sesiones, errores, y archivos cache.disk)

Empaquetar todo. Esto devuelve un archivo .tar que contiene una copia completa de la
aplicacin. Te sugerimos que elimines los archivos temporarios antes de empaquetar una
aplicacin.

Compilar la aplicacin. Si no hay errores, esta opcin generar cdigo bytecodecompiled de todos los mdulos, controladores y vistas. Como las vistas pueden extender
e incluir a otras vistas en una estructura jerrquica, antes de la compilacin, el "rbol" de
vistas se condensa en un archivo nico. El efecto de este procedimiento es que una
aplicacin compilada es ms rpida, porque se omite la lectura de plantillas (parse) y
sustituciones de cadenas durante la ejecucin.

Empaquetar compilados. Esta opcin slo est disponible para aplicaciones


compiladas. Permite empaquetar la aplicacin sin el cdigo fuente para su distribucin
"closed source". Ten en cuenta que Python (como cualquier otro lenguaje de
programacin) puede ser descompilado en la prctica; por lo tanto la compilacin no
garantiza la proteccin del cdigo fuente. Sin embargo, la descompilacin puede ser una
tarea difcil e incluso ilegal.

Eliminar compilados. Simplemente elimina todos los archivos de los modelos, vistas y
controladores bytecode-compiled de la aplicacin. Si la aplicacin se empaquet con
cdigo fuente o se edit localmente, no hay peligro al eliminar los archivos compilados, y
la aplicacin funcionar de todas formas. Si la aplicacin se instal desde un paquete
compilado, entonces la operacin no es segura, porque hay un cdigo fuente hacia el
cual se puedan revertir los cambios, y la aplicacin dejar de funcionar.

Todas las funcionalidades disponibles desde el sitio admin de web2py tambin se pueden
utilizar programticamente a travs de la API definida en el mdulo gluon/admin.py . Basta
con abrir una consola con el intrprete de Python e importar ese mdulo.

Si se ha instalado el SDK de Google App Engine la pgina de la interfaz


administrativa site muestra un botn para desplegar tu aplicacin en el servicio remoto de
GAE. Si se instal python-git , entonces tambin encontrars un botn para aplicar los
cambios en Open Shift. Para instalar aplicaciones en Heroku u otro sistema de alojamiento
puedes buscar el programa correspondiente en la carpeta "scripts".

Acerca de'
La seccin "acerca de" (about) permite editar la descripcin de la aplicacin y su licencia.
Estas ltimas estn escritas respectivamente en los archivos ABOUT y LICENSE en la carpeta
de la aplicacin.

Se pueden utilizar las sintaxis MARKMIN , o gluon.contrib.markdown.WIKI para estos archivos


como se describe en ref.[markdown2] .

Editar
Ya has utilizado la pgina "editar" o design en otra ocasin en este captulo. Aqu queremos
sealar algunas funcionalidades ms de esta pgina.

Si haces clic en cualquier nombre de archivo, puedes visualizar sus contenidos con
resaltado de cdigo fuente.

Si haces clic en editar, puedes editar el archivo a travs de la interfaz web.

Si haces clic en eliminar, puedes eliminar el archivo (en forma permanente).

Si haces clic en test (pruebas), web2py correr los tests. Los tests son creados por el
desarrollador utilizando doctests, y cada funcin debera tener sus propios tests.

Puedes agregar archivos de idiomas, buscar en el dominio de la aplicacin para


detectar todas las cadenas y editar sus traducciones a travs de la interfaz web.

Si los archivos estticos se organizan en carpetas y subcarpetas, las jerarquas de las


carpetas se pueden colapsar o desplegar haciendo clic en el nombre de la carpeta.

La imagen a continuacin muestra la salida de la pgina de tests para la aplicacin welcome.

La imagen que sigue muestra la seccin de idiomas para la aplicacin welcome.

La siguiente imagen muestra cmo se edita un archivo de idioma, en este caso el idioma "it"
(Italiano) para la aplicacin welcome.

El depurador incorporado
(requiere Python 2.6 o superior)
La app admin de web2py incluye un depurador para navegador. Usando el editor en lnea
puedes agregar los breakpoint (instrucciones para detener el flujo de operacin) y, desde la
consola de depuracin asociada al editor, examinar las variables en esos breakpoint y
continuar la ejecucin. Se puede ver un ejemplo de este proceso en la imagen que sigue:

La funcionalidad se basa en el depurador Qdb creado por Mariano Reingart. Esta aplicacin
utiliza
el
mdulo
multiprocessing.connection
para
la
intercomunicacin
entre backend y frontend, por medio de un protocolo de transmisin similar a JSON-RPC. [qdb]

Shell o consola
Si haces clic en el link "shell" debajo de la seccin de controladores en "editar", web2py abrir
una consola de Python para web y ejecutar los modelos para la aplicacin actual. Esto te
permite comunicarte con la aplicacin en forma interactiva.

Ten cuidado cuando usas la shell para navegador - porque las distintas solicitudes de la
consola se ejecutarn en diferentes hilos. Esto puede fcilmente generar errores, en
especial si ests realizando operaciones y ests haciendo pruebas para crear o conectar
a bases de datos. Para este tipo de actividades (por ejemplo, si necesitas
almacenamiento permanente) es preferible usar la lnea de comandos de Python.

Crontab

Tambin debajo de la seccin de controladores en "editar" hay un link a "crontab". Haciendo


clic en este link podrs editar el archivo de crontab de web2py. El crontab de web2py sigue la
misma sintaxis que el crontab para Unix, pero no requiere un sistema Unix. En realidad, slo
requiere web2py, y funciona en Windows. Te permite registrar acciones que se tienen que
ejecutar en segundo plano en horarios programados.
Para ms detalles sobre este tema, consulta el siguiente captulo.

Errores
Mientras programes con web2py, inevitablemente cometers errores e introducirs fallas o
bugs. web2py te ayuda en dos formas: 1) te permite crear tests para cada funcin que pueden
ejecutarse en el navegador desde la pgina "editar"; y 2) cuando se produce un error, se
devuelve un ticket al visitante y el error se reporta/almacena (log).
Introduce intencionalmente un error en la aplicacin de imgenes como se muestra abajo:
def index():
imagenes = db().select(db.imagen.ALL,orderby=db.imagen.titulo)
1/0
return dict(imagenes=imagenes)

Cuando accedas a la accin de index, obtendrs el siguiente ticket:

Slo el administrador puede visualizar el detalle del ticket:

El ticket muestra el traceback (traza o trayectoria del error), y el contenido del archivo que
caus el problema, y el estado completo del sistema (variables, objetos request, session, etc.).
Si el error se produce en la vista, web2py muestra la vista convertida de HTML a cdigo
Python. Esto permite una identificacin ms fcil de la estructura lgica del archivo.

Por defecto los ticket (o tiques) se almacenan en el sistema de archivos y se muestran


agrupados segn la traza del error o traceback. La interfaz administrativa provee de vistas con
estadstica (tipo de traceback y nmero de repeticiones) y una vista detallada (todos los ticket
se listan por id). El administrador puede intercambiar los dos tipos de vistas.
Observa que admin siempre muestra cdigo fuente resaltado (por ejemplo en los reportes de
errores, las palabras especiales de web2py se muestran en naranja). Si haces clic en una
keyword de web2py, eres redirigido a la pgina con la documentacin sobre esa palabra.
Si reparas el bug de divisin por cero en la accin index e introduces otro en la vista de index:
{{extend 'layout.html'}}

<h1>Imgenes registradas</h1>
<ul>
{{for imagen in imagenes:}}
{{1/0}}
{{=LI(A(imagen.titulo, _href=URL("mostrar", args=imagen.id)))}}
{{pass}}
</ul>

obtienes el siguiente ticket:

Ntese que web2py ha convertido la vista de HTML a un archivo Python, y el error descripto
en el ticket se refiere al cdigo Python generado y NO a la vista original:

Esto puede resultar confuso al principio, pero en la prctica hace el trabajo de depuracin ms
sencillo, porque el espaciado (o indentacin) de Python resalta la estructura lgica del cdigo
que has embebido en las vistas.
El cdigo se muestra al final de la misma pgina.

Todos los ticket se listan bajo la aplicacin admin en la pgina "errores" de cada aplicacin:

Mercurial
Si corres la distribucin de cdigo fuente, la interfaz administrativa muestra un tem de men
llamado Versioning (Control de versiones).

Si ingresas un comentario y presionas el botn de aplicar cambios (commit) en la pgina


asociada a ese botn, se aplicarn los cambios de la aplicacin actual. Al aplicar los cambios
por primera vez, se crear un repositorio Mercurial especfico de la aplicacin.
En forma transparente para el usuario, Mercurial almacena informacin sobre los cambios que
se hacen en el cdigo en una carpeta oculta ".hg" ubicada en la subcarpeta de tu aplicacin.
Cada aplicacin tiene su carpeta ".hg" y su propio archivo ".hgignore" (que le dice a Mercurial
qu archivos debe ignorar). Para poder usar esta caracterstica, debes tener instaladas las
libreras para control de versiones de Mercurial (versin 1.9 o superior):
easy_install mercurial

La interfaz web de mercurial no te permite navegar por los cambios previos y sus archivos dif
pero te recomendamos el uso de Mercurial directamente desde la consola o alguno de los
numerosos clientes GUI para Mercurial que son ms potentes. Por ejemplo te permiten
sincronizar tu aplicacin con un repositorio remoto:

Puedes leer ms sobre Mercurial aqu:


http://mercurial.selenic.com/

El Asistente de admin (experimental)


La intefaz admin incluye un asistente que puede ayudarte en la creacin de nuevas
aplicaciones. Puedes acceder al asistente desde la pgina "sites" como se muestra en la
imagen de abajo.

El asistente te guiar a travs de una serie de pasos para la creacin de una nueva aplicacin:
o

Elegir un nombre para la aplicacin

Configurar la aplicacin y elegir los plugin necesarios

Armar los modelos requeridos (crear pginas de ABM/CRUD para cada modelo)

Te permitir editar las vistas de esas pginas utilizando la sintaxis MARKMIN

La imagen de abajo muestra la segunda fase del proceso.

Se puede ver un men desplegable para la eleccin de un plugin de plantilla general


(desde web2py.com/layouts ), un men de opcin mltiple para agregar un conjunto de plugin
extra (desde web2py.com/plugins ) y un campo "login config" para ingresar una "domain:key"
de Janrain.
Las dems fases son un tanto ms simples, por lo que obviamos su explicacin.
El asistente es eficaz para su objetivo pero es considerado una "funcionalidad experimental"
por dos razones:

Las aplicaciones creadas con el asistente y editadas manualmente ya no pueden ser


ms modificadas por el asistente.

La interfaz del asistente cambiar eventualmente para incluir soporte para ms


caractersticas y un desarrollo visual ms fcil.

En todo caso el asistente es una herramienta til para crear prototipos velozmente y puede
servir como plantilla (bootstrap) de una aplicacin con un diseo alternativo y otro conjunto de
plugin.

Configurando admin
Normalmente no hay necesidad de modificar ninguna configuracin en admin aunque son
posibles algunas personalizaciones. Luego de autenticarte en admin puedes editar la
configuracin a travs la URL:
http://127.0.0.1:8000/admin/default/edit/admin/models/0.py

Ten en cuenta cmo admin puede utilizarse para auto-modificarse. De hecho admin es una
app como cualquier otra.
El archivo "0.py" contiene suficientemente documentacin. De todas formas, aqu se muestran
las personalizaciones ms importantes que puedes necesitar:
GAE_APPCFG = os.path.abspath(os.path.join('/usr/local/bin/appcfg.py'))

Esto debera apuntar a la ubicacin del archivo "appcfg.py" que viene con el SDK de Google
App Engine. Si tienes el SDK quizs quieras cambiar estos parmetros de configuracin a los
valores correctos. Esto te permitir desplegar en GAE desde la interfaz de admin.
Adems puedes configurar la app admin de web2py en modo demo:
DEMO_MODE = True
FILTER_APPS = ['welcome']

Y slo las aplicaciones listadas en FILTER_APPS estarn disponibles y slo se podr acceder
a ellas en modo de solo-lectura.
Si eres docente y quieres exponer la interfaz administrativa a estudiantes para que puedan
compartir una interfaz administrativa para sus proyectos (piensa en un laboratorio virtual), lo
puedes hacer configurando:
MULTI_USER_MODE = True

De esa forma los estudiantes debern autenticarse y slo podrn acceder a sus propias
aplicaciones a travs de admin. T, como el usuario principal/maestro, tendrs acceso a todas.
En modo multiusuario, puedes registrar estudiantes usando un link para el registro mltiple
o bulk register y administrarlos usando el link correspondiente (manage students''). El sistema
adems llevar un registro de los accesos de los estudiantes y de la cantidad de lneas que
agregan o eliminan de su cdigo fuente. Esta informacin puede ser consultada por el
administrador por medio de grficos en la pgina "acerca de" de la aplicacin.
Ten en cuenta que este mecanismo de todas formas asume que todos los usuarios son
confiables. Todas las aplicaciones creadas bajo admin corren con las mismas credenciales en
el mismo sistema de archivos. Es posible para una aplicacin creada por un estudiante el
acceso a los datos y el cdigo fuente de una app de otro estudiante. Adems sera posible
para un estudiante crear una app que prohba el acceso al servidor.

admin mvil
Ten en cuenta que la aplicacin admin incluye el plugin "plugin_jqmobile", que incluye la
librera jQuery Mobile. Cuando se accede a la app admin desde un dispositivo mvil, web2py
lo detectar y mostrar una interfaz grfica apta para el dispositivo.

Ms acerca de appadmin
appadmin no est pensada para ser expuesta al pblico. Est diseada para ayudarte como
forma de fcil acceso a la base de datos. Consiste de slo dos archivos: un controlador
"appadmin.py" y una vista "appadmin.html", que son utilizados por todas las acciones en el
controlador.
El controlador de appadmin es relativamente pequeo y legible; sirve adems como ejemplo
para el diseo de una interfaz de acceso a la base de datos.
appadmin muestra cuales bases de datos estn disponibles y qu tablas existen en cada
base de datos. Puedes insertar registros y listar todos los registros para cada tabla

individualmente. appadmin hace una separacin en pginas de la salida por cada 100
registros.
Una vez que se selecciona un set de registros, el encabezado de las pginas cambia,
permitindote actualizar o eliminar los registros devueltos por la consulta.
Para actualizar los registros, ingresa un criterio SQL en el campo para la cadena de la
consulta:
title = 'prueba'

donde los valores de la cadena deben estar entre comillas simples. Los criterios mltiples se
pueden separar con comas.
Para eliminar un registro, haz clic en el checkbox para confirmar que ests seguro.
appadmin tambin puede realizar consultas tipo join si el FILTRO de SQL contiene una
instruccin condicional de SQL que tenga dos o ms tablas. Por ejemplo, prueba con:
db.imagen.id == db.publicacion.imagen_id

web2py le pasa el comando a la DAL, que entiende que la consulta asocia dos tablas; as, las
dos tablas se consultan con un INNER JOIN. Esta es la salida:

Si haces clic en el nmero de un campo id, obtienes una pgina de edicin para el registro
correspondiente.
Si haces clic en el nmero de un campo tipo reference, obtienes una pgina de edicin para el
registro de referencia.
No se pueden actualizar o eliminar registros consultados con un join, porque implicara la
modificacin de registros de mltiples tablas y podra resultar confuso.
Aparte de sus funciones para administracin de la base de datos, appadmin adems te
permite visualizar el detalle de los contenidos del cache de la aplicacin

(en /tuapp/appadmin/cache )

as

como

tambin

los

contenidos

de

los

objetos request , response , y session (en /tuapp/appadmin/state ).

appadmin reemplaza response.menu con su propio men, que incluye, para la app
actual, accesos a la pgina edit en admin, la pgina db(administracin de la base de
datos), la pgina state, y la pgina cache. Si la plantilla general de tu aplicacin no
genera un men usando response.menu , entonces no vers el men de appadmin. En
este caso, puedes modificar el archivo appadmin.html y
agregar {{=MENU(response.menu)}} para mostrar el men.

El ncleo
Opciones de la lnea de comandos
Es posible omitir el uso de la GUI e iniciar web2py directamente desde la lnea de comandos
escribiendo algo como:
python web2py.py -a 'tu contrasea' -i 127.0.0.1 -p 8000

Cuando web2py inicie, crear un archivo llamado "parameters_8000.py" donde se almacenar


el hash (la codificacin) de la contrasea. Si especificas como contrasea "<ask>", web2py te
pedir que ingreses la contrasea al iniciar.
Para mayor seguridad, puedes iniciar web2py con:
python web2py.py -a '<recycle>' -i 127.0.0.1 -p 8000

En este caso web2py reutiliza la contrasea previamente codificada y almacenada. Si no se


provee de una contrasea, o si se ha borrado el archivo "parameters_8000.py", la interfaz
administrativa web se deshabilitar.
En algunos sistemas Unix/Linux, si la contrasea es
<pam_user:un_usuario>

web2py usa la contrasea PAM de la cuenta en el Sistema Operativo del usuario especificado
para la autenticacin como administrador, a menos que la configuracin de PAM bloquee el
acceso.

Normalmente web2py corre con CPython (la implementacin en C del intrprete de Python
creada por Guido van Rossum), per tambin puede correr con PyPy y Jython. Esta

ltima posibilidad te permite usar web2py en el contexto de una infraestructura J2EE. Para
usar Jython, simplemente reemplaza "python web2py.py ..." por "jython web2py.py". Los
detalles sobre la instalacin de los mdulos Jython y zxJDBC requeridos para el acceso a
las bases de datos se puede consultar en el Captulo 14.
El script "web2py.py" puede tomar varios argumentos de la lnea de comandos especificando
el nmero mximo de hilos, el uso de SSL, etc. Para una lista completa escribe:
>>> python web2py.py -h
Forma de uso: python web2py.py

Script de inicio del marco de desarrollo web web2py


ADVERTENCIA: si no se especifica una contrasea (-a 'contrasea'),
web2py intentar ejecutar una GUI, en este caso las opciones de
la lnea de comandos se omitirn.

Opciones:
--version

muestra la versin del programa y sale

-h, --help

muestra esta lista de ayuda y sale

-i IP, --ip=IP

la direccin IP del servidor (e.g., 127.0.0.1 or ::1);


Nota: Este valor se ignora cuando se usa la opcin 'interfaces'.

-p PUERTO, --port=PUERTO puerto del servidor (8000)


-a CONTRASEA, --password=CONTRASEA
contrasea que se usar para la cuenta administrativa
(usa -a "<recycle>" para reutilizar la ltima contrasea
almacenada)
-c CERTIFICADO_SSL, --ssl_certificate=CERTIFICADO_SSL
archivo que contiene el certificado ssl
-k CLAVE_PRIVADA_SSL, --ssl_private_key=CLAVE_PRIVADA_SSL
archivo que contiene la clave privada ssl
--ca-cert=CERTIFICADO_CA_SSL

Usa este archivo conteniendo el certificado CA


para validar los certificados X509 de los clientes
-d ARCHIVO_PID, --pid_filename=ARCHIVO_PID
archivo que almacena el pid del servidor
-l ARCHIVO_LOG, --log_filename=ARCHIVO_LOG
archivo para llevar un registro de las conexiones
-n CANTHILOS, --numthreads=CANTHILOS
cantidad de hilos (obsoleto)
--minthreads=MNHILOS
nmero mnimo de hilos del servidor
--maxthreads=MAXHILOS
nmero mximo de hilos del servidor
-s NOMBRE_SERVIDOR, --server_name=NOMBRE_SERVIDOR
nombre asignado al servidor web
-q TAM_COLA_SOLICITUD, --request_queue_size=REQUEST_QUEUE_SIZE
mximo nmero de solicitudes en la cola cuando el
servidor no est disponible
-o VENCIMIENTO, --timeout=VENCIMIENTO
tiempo lmite de espera para cada solicitud (10 segundos)
-z VENC_CIERRE, --shutdown_timeout=VENC_CIERRE
tiempo lmite de espera para cerrar el servidor (5 segundos)
--socket-timeout=VENCIMIENTO_SOCKET
tiempo lmite para el ''socket'' (5 segundos)
-f CARPETA, --folder=CARPETA
carpeta desde la cual correr web2py
-v, --verbose
-Q, --quiet

incremento de la salida de depuracin de --test


deshabilita toda salida

-D NIVEL_DEPURACIN, --debug=NIVEL_DEPURACIN
establece el nivel de la salida de depuracin

(0-100, 0 es todo, 100 es nada; por defecto es 30)


-S NOMBRE_APP, --shell=NOMBRE_APP
corre web2py en la consola shell interactiva de
IPython (si est disponible) con el nombre
especificado de la app (si la app no existe se
crear). NOMBRE_APP tiene el formato a/c/f (c y f
son opcionales)
-B, --bpython

corre web2py en la shell interactiva o en bpython (si


se instal) con el nombre especificado (si no existe
la app se crear). Usa esta opcin en combinacin con
--shell

-P, --plain

usar nicamente la shell de Python; se debera usar


con la opcin --shell

-M, --import_models importar automticamente los archivos del modelo;


por defecto es False; se debera usar con la opcin
--shell
-R ARCHIVO_PYTHON, --run=ARCHIVO_PYTHON
correr el archivo de python en un entorno de web2py;
se debera usar con la opcin --shell
-K PLANIFICADOR, --scheduler=PLANIFICADOR
correr tareas planificadas para las app especificadas:
lee una lista de nombres de apps del tipo
-K app1,app2,app3 o una lista con grupos como
-K app1:grupo1:grupo2,app2:grupo1 para sobrescribir
nombres especficos de grupos. (solo cadenas, no se
admiten los espacios. Requiere definir un planificador
en los modelos)
-X, --with-scheduler corre el planificador junto con el servidor web
-T RUTA_PRUEBAS, --test=RUTA_PRUEBAS

corre las pruebas ''doctest'' en el entorno de web2py;


RUTA_PRUEBAS tiene el formato a/c/f (c y f son opcionales)
-W SERVICIOWIN, --winservice=SERVICIOWIN
control del servicio de Windows
-W install|start|stop
-C, --cron

activa una lista de tareas cron en forma manual;


usualmente se llama desde un crontab del sistema

--softcron
-Y, --run-cron
-J, --cronjob

activa el uso de softcron


iniciar como proceso en segundo plano
identificar un comando iniciado por cron

-L CONFIG, --config=CONFIG
archivo de configuracin
-F ARCHIVO_PROFILER, --profiler=ARCHIVO_PROFILER
nombre de archivo del profiler
-t, --taskbar

usar la gui de web2py y correr en la barra de


tareas o ''taskbar'' (bandeja del sistema)

--nogui

solo texto, sin GUI

-A ARGUMENTOS, --args=ARGUMENTOS se debe completar con una lista de


argumentos a pasarse al script;
se utiliza en conjunto con -S.
-A debe ser la ltima opcin
--no-banner

No mostrar la pantalla de inicio

--interfaces=INTERFACES
aceptar conexiones para mltiples direcciones:
"ip1:puerto1:clave1:cert1:ca_cert1;
ip2:puerto2:clave2:cert2:ca_cert2;..."
(:clave:cert:ca_cert es opcional; no debe contener espacios;
las direcciones IPv6 deben llevar corchetes [])
--run_system_tests

corre las pruebas para web2py

Las opciones en minsculas se usan para configurar el servidor web. La opcin -L le dice a
web2py que lea las opciones de configuracin desde un archivo, -W instala web2py como
servicio de Windows, mientras que las opciones -S , -P y -M inician una sesin interactiva de
la consola de Python. La opcin -T busca y ejecuta las pruebas doctest en un entorno de
ejecucin de web2py. Por ejemplo, el siguiente ejemplo corre los doctest para todos los
controladores en la aplicacin "welcome":
python web2py.py -vT welcome

Si ejecutas web2py como servicio de Windows, -W , no es conveniente pasar los parmetros


de configuracin por medio de los argumentos de la lnea de comandos. Por esa razn, en la
carpeta de web2py se puede ver un ejemplo de archivo de configuracin "options_std.py" para
el servidor web incorporado:
import socket
import os

ip = '0.0.0.0'
port = 80
interfaces = [('0.0.0.0', 80)]
#,('0.0.0.0',443,'clave_privada_ssl.pem','certificado_ssl.pem')]
password = '<recycle>' # <recycle> significa que se usar la contrasea previamente
almacenada
pid_filename = 'servidorhttp.pid'
log_filename = 'servidorhttp.log'
profiler_filename = None
ssl_certificate = None # 'certificado_ssl.pem' # ## ruta al archivo con el certificado
ssl_private_key = None # 'clave_privada_ssl.pem' # ## ruta al archivo con la clave privada
#numthreads = 50 # ## obsoleto; eliminar
minthreads = None
maxthreads = None
server_name = socket.gethostname()
request_queue_size = 5

timeout = 30
shutdown_timeout = 5
folder = os.getcwd()
extcron = None
nocron = None

Este archivo contiene los valores por defecto de web2py, debes importarlo en forma explcita
con la opcin de lnea de comandos -L . Solo funcionar cuando corras web2py como servicio
de Windows.

Flujo de trabajo o workflow


El flujo de operacin de web2py es el siguiente:
o

El servidor web recibe una solicitud HTTP (el servidor web incorporado Rocket u otro
servidor web conectado a web2py a travs de WSGI u otro adaptador). El servidor web
administra cada solicitud en su propio hilo, en forma paralela.

Se analiza el encabezado HTTP y se pasa al administrador de direcciones (dispatcher,


descripto ms adelante en este captulo).

El administrador de direcciones decide cul de las aplicaciones manejar la solicitud y


asocia la informacin en PATH_INFO del URL con una llamada a una funcin. Cada URL
se corresponde con una llamada a una funcin.

Las solicitudes de archivos de la carpeta static se sirven en forma directa, y los


archivos extensos se transmiten al cliente automticamente usando un stream.

Toda solicitud que no est asociada a un archivo esttico se asocia a una accin (es
decir, a una funcin en un archivo del controlador, en la aplicacin solicitada).

Antes de llamar a la accin, suceden algunas cosas: si el encabezado de la solicitud


contiene una cookie de sesin para la app, se recupera el objeto de la sesin ( session),
si no, se crea una sesin nueva (pero el archivo de la sesin no se almacenar
inmediatamente); se crea un ambiente de ejecucin para la solicitud; los modelos se
ejecutan en ese entorno.

Por ltimo, se ejecuta la accin del controlador en el entorno creado previamente.

Si la accin devuelve una cadena, se devolver al cliente (o si la accin devuelve un


objeto ayudante HTML de web2py, se devolver la serializacin del ayudante).

Si la accin devuelve un iterable, el cliente recibir un stream de datos generado por


un bucle que recorre ese objeto.

Si la accin devuelve un diccionario, web2py intentar ubicar la vista para convertir el


diccionario. La vista debe tener el mismo nombre que la accin (a menos que se haya
especificado otro), y la misma extensin que la pgina solicitada (por defecto es .html); si
se produce una falla, web2py puede recuperar una vista genrica (si est disponible y
habilitada). La vista tiene acceso a toda variable definida en los modelos as como
tambin el contenido del diccionario devuelto por la accin, pero no tiene acceso a las
variables globales definidas en el controlador.

La totalidad del cdigo del usuario se ejecuta en el mbito de una nica transaccin de
la base de datos, a menos que se especifique lo contrario.

Si el cdigo del usuario finaliza la ejecucin con xito, se aplicarn los cambios en la
base de datos.

Si se produce una falla en la ejecucin del cdigo del usuario, la traza del error ( error
traceback) se almacena en un ticket, y el id del ticket se informa en la respuesta al
cliente. Solo el administrador del sistema puede buscar y leer las trazas de error incluidas
en los tickets.

Hay algunos detalles a tener en cuenta:


o

Los modelos que pertenecen a la misma carpeta se ejecutan en orden alfabtico.

Toda variable definida en el modelo ser visible para los otros modelos que le sigan en
orden alfabtico, para los controladores y para las vistas.

Los modelos en subcarpetas se ejecutan condicionalmente. Por ejemplo, si el usuario


solicit "a/c/f" donde "a" es la aplicacin, "c" el controlador y "f" la funcin (accin),
entonces se ejecutarn los siguientes modelos:

applications/a/models/*.py
applications/a/models/c/*.py
applications/a/models/c/f/*.py

Se ejecutar el controlador solicitado y se llamar a la funcin solicitada. Esto implica


que el cdigo del nivel superior en el controlador tambin se ejecuta para cada solicitud
que corresponda a ese controlador.

o
o

La vista se llama nicamente cuando la accin devuelve un diccionario.


Si no se encuentra la vista, web2py intenta usar una vista genrica. Por defecto, las
vistas genricas estn deshabilitadas, a menos que la app de andamiaje incluya una
lnea en /models/db.py para habilitarlas restringindolas para su uso en localhost. Las
vistas genricas se pueden habilitar en funcin del tipo de extensin y en funcin de la
accin (usando response.generic_patterns ). En general, las vistas genricas son una
herramienta de desarrollo y normalmente no se deberan usar en produccin. Si quieres
que algunas acciones usen las vistas genricas, agrega esas acciones
en response.generic_patterns (descripto con ms detalle en el captulo dedicado a los
servicios).

Los comportamientos posibles para una accin son los siguientes:


Devuelve una cadena
def index(): return 'datos'

Devuelve un diccionario para una vista:


def index(): return dict(key='value')

Devuelve todas las variables locales:


def index(): return locals()

Redirigir al usuario a otra pgina:


def index(): redirect(URL('otra_accion'))

Devolver otra respuesta HTTP distinta a "200 OK":


def index(): raise HTTP(404)

Devolver un ayudante (por ejemplo, un FORM):


def index(): return FORM(INPUT(_name='prueba'))

(esto se usa ms que nada para llamadas de retorno con Ajax y para componentes, para ms
informacin puedes consultar el captulo 12)

Cuando una accin devuelve un diccionario, el diccionario puede contener objetos generados
por ayudantes, incluyendo formularios creados a partir de tablas de la base de datos o
formularios creados por un creador de formularios o form factory, por ejemplo:
def index(): return dict(formulario=SQLFORM.factory(Field('nombre')).process())

(todos los formularios generados por web2py usan el mtodo postback, ver captulo 3)

Administracin de direcciones o Dispatching


web2py asocia los URL con el formato:
http://127.0.0.1:8000/a/c/f.html

con la funcin f() en el controlador "c.py" de la aplicacin "a". Si no se encuentra un f ,


web2py usa por defecto la funcin index del controlador. Si no se encuentra un c , entonces
web2py usa por defecto el controlador "default.py", y si no se encuentra una aplicacin a ,
web2py usa por defecto la aplicacin init . Si no existe una aplicacin init , web2py intentar
ejecutar la aplicacin welcome . Esto se muestra en un esquema en la imagen de abajo:

Por defecto, toda nueva solicitud crear una nueva sesin. Adems, se devuelve una cookie
de sesin al navegador cliente para mantener un registro y control de esa sesin.
La extensin .html es opcional; .html se asume por defecto. La extensin determina la
extensin de la vista que procesa y convierte la salida de la funcin f() del controlador. Esto
permite que el mismo contenido se pueda servir en mltiples formatos (html, xml, json, rss,
etc.).

Las funciones que toman argumentos o comienzan con un doble guin no se exponen
pblicamente y solo pueden ser llamadas por otras funciones.
Existe una excepcin para el caso de los URL que tienen la forma:
http://127.0.0.1:8000/a/static/nombredearchivo

No hay un controlador llamado "static". web2py interpreta esto como una solicitud de un
archivo llamado "nombredearchivo" en la subcarpeta "static" de la aplicacin "a".

Cuando se descargan archivos estticos, web2py no crea sesiones ni devuelve una cookie de
sesin ni tampoco ejecuta los modelos. web2py siempre crea un stream para los archivos
estticos en bloques de 1MB y enva un mensaje PARTIAL CONTENT cuando recibe del
cliente una solicitud RANGE de una parte del archivo.
Adems web2py soporta el protocolo IF_MODIFIED, y no enva el archivo si ya se ha
almacenado en el cach de navegacin y si el archivo no se modific posteriormente.
Cuando se crea un link a un archivo de audio o video de la carpeta static, si quieres hacer que
el navegador descargue el archivo en lugar de hacer una descarga por streamming con un
reproductor de medios, agrega ?attachment al URL. Esto le dice a web2py que debe
establecer el encabezado Content-Disposition de la respuesta HTTP como "attachment"
(adjunto). Por ejemplo:
<a href="/app/static/mi_archivo_de_audio.mp3?attachment">Descargar</a>

Cuando se hace clic en el link de arriba, el navegador le mostrar una opcin de descarga del
MP3 en lugar de iniciar la transmisin del audio. (Como se detalla ms abajo, puedes adems
establecer los encabezados de la respuesta HTTP directamente almacenando un diccionario
con los nombres de los encabezados y sus valores en response.headers .)
web2py asocia las solicitudes GET/POST con la forma:
http://127.0.0.1:8000/a/c/f.html/x/y/z?p=1&q=2

a una funcin f en el controlador "c.py" de la aplicacin a , y almacena los parmetros del


URL en la variable request de la siguiente forma:
request.args = ['x', 'y', 'z']

y:
request.vars = {'p':1, 'q':2}

y tambin:
request.application = 'a'
request.controller = 'c'
request.function = 'f'

En el ejemplo de arriba, se puede usar tanto request.args[i] como request.args(i) para


recuperar el i-simo elemento de request.args , la diferencia es que la primera notacin genera
una excepcin cuando la lista no tiene el ndice especificado, mientras que la segunda
devuelve None en ese caso.
request.url

almacena el URL completo de la solicitud actual (no incluye las variables GET).
request.ajax

por defecto es False pero se establece como True si web2py determina que la accin fue
solicitada por medio de Ajax.
Si la solicitud es una solicitud Ajax y fue iniciada por un componente de web2py, el nombre del
componente se puede recuperar con:
request.cid

Los componentes se tratan con ms detalla en el Captulo 12.


Si la solicitud HTTP es de tipo GET, entonces request.env.request_method se establece como
"GET"; si es POST, request.env.request_method tomar el valor "POST", las variables de
consulta del URL se almacenan en el diccionario Storage request.vars ; tambin se almacenan
en request.get_vars (en el caso de una solicitud POST) o request.post_vars (para solicitudes
POST).
web2py almacena las variables de su propio entorno y las del entorno WSGI en request.env ,
por ejemplo:
request.env.path_info = 'a/c/f'

y los encabezados HTTP en variables de entorno, por ejemplo:


request.env.http_host = '127.0.0.1:8000'

Ten en cuenta que web2py valida todos los URL para evitar ataques de tipo "directory
traversal".
Los URL slo pueden contener caracteres alfanumricos, subguiones y barras;
los args (argumentos) pueden contener puntos no consecutivos. Los espacios se reemplazan

por subguiones antes de la validacin. Si la sintaxis del URL no es vlida, web2py devuelve un
mensaje con el cdigo de error HTTP 400[http-w] [http-o].
Si el URL corresponde a una solicitud de un archivo esttico, web2py simplemente lo lee y
transmite el archivo solicitado por medio de un stream.
Si el URL no solicita un archivo esttico, web2py procesa la solicitud en el siguiente orden:
o

Analiza y recupera las cookie.

Crea un entorno para ejecutar la funcin.

Inicializa los objetos request , response y cache .

Abre el objeto session existente o crea uno nuevo.

Ejecuta los modelos que corresponden a la aplicacin solicitada.

Ejecuta la funcin del controlador que corresponde a la accin solicitada.

Si la funcin devuelve un diccionario, ejecuta la vista asociada.

En caso de finalizar exitosamente, aplica los cambios de las transacciones pendientes.

Guarda la sesin.

Devuelve una respuesta HTTP.

Ten en cuenta que el controlador y la vista se ejecutan en distintas copias del mismo entorno;
por lo tanto, la vista no puede examinar el controlador, pero si tiene acceso al modelo y a las
variables devueltas por la funcin del controlador correspondiente a la accin.
Si se genera una excepcin (que no sea de tipo HTTP), web2py hace lo siguiente:
o

Almacena la traza del error en un archivo y le asigna un nmero de ticket.

Recupera el estado inicial de todas las transacciones de la base de datos.

Devuelve una pgina de error informando el nmero de ticket.

Si la excepcin generada es de tipo HTTP , se interpretar como el comportamiento normal


(por ejemplo, una redireccin HTTP ), y se aplican los cambios a todas las transacciones
abiertas. El comportamiento posterior est especificado por el tipo de excepcin HTTP mismo.

La clase de excepcin HTTP no es una excepcin estndar de Python; est definida en


web2py.

Libreras
Las libreras de mdulos de web2py se exponen a las aplicaciones del usuario como objetos
del espacio de nombres global. Por ejemplo ( request , response , session o cache ), clases
(ayudantes, validadores, la API de DAL), y funciones ( T y redirect ).
Estos objetos estn definidos en los siguientes archivos:
web2py.py
gluon/__init__.py

gluon/highlight.py gluon/restricted.py gluon/streamer.py

gluon/admin.py

gluon/html.py

gluon/cache.py

gluon/http.py

gluon/cfs.py

gluon/rewrite.py
gluon/rocket.py

gluon/template.py
gluon/storage.py

gluon/import_all.py gluon/sanitizer.py gluon/tools.py

gluon/compileapp.py gluon/languages.py gluon/serializers.py gluon/utils.py


gluon/contenttype.py gluon/main.py
gluon/dal.py
gluon/decoder.py

gluon/myregex.py
gluon/newcron.py

gluon/settings.py
gluon/shell.py
gluon/sql.py

gluon/fileutils.py gluon/portalocker.py gluon/sqlhtml.py


gluon/globals.py

gluon/validators.py

gluon/widget.py
gluon/winservice.py
gluon/xmlrpc.py

gluon/reserved_sql_keywords.py

Observa que muchos de esos mdulos, en especial dal (la capa de abstraccin de la
base de datos), template (el lenguaje de plantillas), rocket (el servidor web), y html (los
ayudantes) no tienen dependencias y se pueden usar fuera de web2py.
La app de andamiaje comprimida con tar y gzip que viene con web2py es
welcome.w2p

Esta es creada durante la instalacin y se sobrescribe al hacer un upgrade.

Cuando corres web2py por primera vez, se crean dos carpetas: deposit y applications. La
carpeta deposit se usa como espacio de almacenamiento temporal para la instalacin y
desinstalacin de aplicaciones. Si inicias web2py por primera vez y adems despus de

un upgrade, la app "welcome" se comprime en el archivo "welcome.w2p" para usarse


como app de andamiaje.
Cuando se hace un upgrade de web2py, esta actualizacin viene con un archivo llamado
"NEWINSTALL". Si web2py encuentra ese archivo, entiende que se ha hecho un upgrade,
elimina ese archivo y crea un nuevo archivo "welcome.w2p".
La versin actual de web2py se almacena en el campo "VERSION" y sigue las reglas
semnticas estndar para el control de versiones donde el id de la versin del programa (build
id) es la fecha y hora (timestamp).
Las pruebas unit-test estn en
gluon/tests/

Hay controladores para conexin a varios servidores web:


cgihandler.py

# no se recomienda

gaehandler.py

# para Google App Engine

fcgihandler.py

# para FastCGI

wsgihandler.py

# para WSGI

isapiwsgihandler.py # para IIS


modpythonhandler.py # obsoleto

("fcgihandler" utiliza "gluon/contrib/gateways/fcgi.py" desarrollado por Allan Saddi) y


anyserver.py

que es un script para interfaz con distintos tipos de servidor web, descripto en el Captulo 13.
Hay tres archivos de ejemplo:
options_std.py
routes.example.py
router.example.py

El primero es un archivo con opciones de configuracin que se puede pasar a web2py.poy con
el parmetro -L . El segundo es un ejemplo de archivo para mapeo de URL (url mapping).
Este ltimo se cargar automticamente cuando se cambie su nombre a "routes.py". El tercero

es una sintaxis alternativa para el mapeo de URL, y tambin se puede renombrar (o copiar
como) "routes.py".
Los archivos
app.example.yaml
queue.example.yaml

Son ejemplos de archivos de configuracin usados para el despliegue en Google App Engine.
Puedes leer ms acerca de ellos en el captulo sobre recetas de implementacin y en las
pginas de la documentacin de Google.
Hay otras libreras adicionales, algunas de ellas son software de terceros:
feedparser[feedparser] de Mark Pilgrim para la lectura fuentes RSS y Atom:
gluon/contrib/__init__.py
gluon/contrib/feedparser.py

markdown2[markdown2] de Trent Mick para el lenguaje de marcado wiki:


gluon/contrib/markdown/__init__.py
gluon/contrib/markdown/markdown2.py

markmin markup:
gluon/contrib/markmin

fpdf creado por Mariano Reingart para la generacin de documentos PDF:


gluon/contrib/fpdf

Esta librera no est documentada en este texto pero est alojada y documentada aqu:
http://code.google.com/p/pyfpdf/

pysimplesoap es una implementacin ligera del servidor SOAP creada por Mariano Reingart:
gluon/contrib/pysimplesoap/

simplejsonrpc es cliente para JSON-RPC ligero, tambin creado por Mariano Reingart:

gluon/contrib/simplejsonrpc.py

memcache[memcache] API Python de Evan Martin:


gluon/contrib/memcache/__init__.py
gluon/contrib/memcache/memcache.py

redis_cache
es un mdulo para el almacenamiento de cach en la base de datos redis:
gluon/contrib/redis_cache.py

gql, un port o adaptacin de DAL para Google App Engine:


gluon/contrib/gql.py

memdb, una adaptacin de DAL que funciona sobre memcache:


gluon/contrib/memdb.py

gae_memcache es una API para el uso de memcache en Google App Engine:


gluon/contrib/gae_memcache.py

pyrtf[pyrtf] para la generacin de documentos Rich Text Format (RTF), desarrollado por Simon
Cusack y revisado por Grant Edwards:
gluon/contrib/pyrtf/

PyRSS2Gen[pyrss2gen] desarrollado por Dalke Scientific Software, para la crear fuentes de RSS:
gluon/contrib/rss2.py

simplejson[simplejson] de Bob Ippolito, la librera estndar para la lectura, anlisis y escritura de


objetos JSON:
gluon/contrib/simplejson/

Google Wallet [googlewallet] provee de botones


procesamiento de pagos de Google:
gluon/contrib/google_wallet.py

"pagar

ahora"

enlazados

al

sistema

de

Stripe.com [stripe] provee de una API simple para aceptar pagos con tarjeta de crdito:
gluon/contrib/stripe.py

AuthorizeNet [authorizenet] provee de una simple API para aceptar pagos con tarjeta de crdito a
travs de la red Authorize.net
gluon/contrib/AuthorizeNet.py

Dowcommerce [dowcommerce] API para operaciones con tarjetas de crdito:


gluon/contrib/DowCommerce.py

PaymentTech API para operaciones con tarjetas de crdito:


gluon/contrib/paymentech.py

PAM[PAM] API de autenticacin creada por Chris AtLee:


gluon/contrib/pam.py

Un clasificador bayesiano para crear registros ficticios de la base de datos utilizados para
pruebas:
gluon/contrib/populate.py

Un archivo con una API para correr aplicaciones en Heroku.com:


gluon/contrib/heroku.py

Un archivo que permite la interaccin con la barra de tareas de Windows, cuando web2py
corre como servicio:
gluon/contrib/taskbar_widget.py

Mtodos opcionales de acceso (login_methods) y formularios (login_form) para la


autenticacin:
gluon/contrib/login_methods/__init__.py
gluon/contrib/login_methods/basic_auth.py
gluon/contrib/login_methods/browserid_account.py
gluon/contrib/login_methods/cas_auth.py

gluon/contrib/login_methods/dropbox_account.py
gluon/contrib/login_methods/email_auth.py
gluon/contrib/login_methods/extended_login_form.py
gluon/contrib/login_methods/gae_google_account.py
gluon/contrib/login_methods/ldap_auth.py
gluon/contrib/login_methods/linkedin_account.py
gluon/contrib/login_methods/loginza.py
gluon/contrib/login_methods/oauth10a_account.py
gluon/contrib/login_methods/oauth20_account.py
gluon/contrib/login_methods/oneall_account.py
gluon/contrib/login_methods/openid_auth.py
gluon/contrib/login_methods/pam_auth.py
gluon/contrib/login_methods/rpx_account.py
gluon/contrib/login_methods/x509_auth.py

adems web2py contiene una carpeta con scripts que pueden ser de ayuda incluyendo
scripts/setup-web2py-fedora.sh
scripts/setup-web2py-ubuntu.sh
scripts/setup-web2py-nginx-uwsgi-ubuntu.sh
scripts/setup-web2py-heroku.sh
scripts/update-web2py.sh
scripts/make_min_web2py.py
...
scripts/sessions2trash.py
scripts/sync_languages.py
scripts/tickets2db.py
scripts/tickets2email.py
...
scripts/extract_mysql_models.py

scripts/extract_pgsql_models.py
...
scripts/access.wsgi
scripts/cpdb.py

Los setup-web2py-* son especialmente tiles porque realizan una instalacin y configuracin
ntegra en ambientes de produccin desde cero.
Algunos de ellos se detallan en el Captulo 14, pero todos incluyen documentacin explicando
sus caractersticas y opciones.
Por ltimo, web2py incluye estos archivos necesarios para crear las distribuciones binarias.
Makefile
setup_exe.py
setup_app.py

Estos son script de configuracin para py2exe y py2app, respectivamente, y slo son
requeridos para crear la distribucin binaria de web2py. NO ES NECESARIA SU EJECUCIN.
Las aplicaciones de web2py contienen archivos adicionales, particularmente libreras de
JavaScript, como jQuery, calendar y codemirror. Los crditos para cada proyecto estn
documentados en sus respectivos archivos.

Applications
Las aplicaciones de web2py se componen de las siguientes partes:
o

models describe una representacin de la informacin en funcin de tablas de la base


de datos y relaciones entre tablas.
controllers describe los algoritmos de la aplicacin y su flujo de trabajo.

views describe cmo la informacin se debera presentar al usuario usando HTML y


JavaScript.

languages describe cmo traducir las cadenas de la aplicacin a los distintos


lenguajes soportados.

static files los archivos estticos no requieren procesamiento (por ejemplo imgenes,
hojas de estilo CSS, etc).

ABOUT y README son documentos cuyo significado y uso es obvio.

errors almacena los reportes de errores generados por la aplicacin.

sessions almacena la informacin relacionada con cada usuario particular.

databases almacena bases de datos SQLite e informacin adicional de las tablas.

cache almacena los tems de aplicaciones en cach.

modules son los mdulos opcionales Python.

private los controladores tienen acceso a los archivos privados, mientras que los
desarrolladores no pueden acceder a ellos directamente.

uploads los modelos tienen acceso a los archivos en uploads, pero no estn
disponibles directamente para el desarrollador (por ejemplo, los archivos subidos por
usuarios de la application).

tests es un directorio para almacenar script de pruebas, y programas fixture o mock.

Se puede acceder a los modelos, vistas, controladores, idiomas y archivos estticos a travs
de la interfaz administrativa [design]. Tambin se puede acceder a ABOUT, README y los
errores a travs de la interfaz administrativa por medio del tem de men correspondiente. La
aplicacin tiene acceso a los archivos de sesin, cach, mdulos y privados pero no a travs
de la interfaz administrativa.
Todo est prolijamente organizado en una clara estructura de directorios que se reproduce en
cada aplicacin instalada, si bien el usuario no necesita acceder al sistema de archivos en
forma directa:
__init__.py ABOUT
controllers modules
cache

errors

LICENSE
private
upload

models
tests

views
cron

sessions static

"__init__.py" es un archivo vaco que es requerido para que Python (y web2py) pueda importar
los mdulos en el directorio modules .

Observa que la aplicacin admin simplemente provee de una interfaz web para las
aplicaciones en el sistema de archivos del servidor. Las aplicaciones de web2py tambin se
pueden crear y desarrollar desde la lnea de comandos y tambin puedes desarrollar las
aplicaciones usando tu editor preferido de texto o IDE; no ests obligado a usar la interfaz
administrativa para navegador. Se puede crear una nueva aplicacin en forma manual si se
reproduce la estructura de directorio detallada arriba en una subcarpeta, por ejemplo,
"applications/nuevaapp/"
(o
simplemente
descomprimiendo
con
tar
el
archivo welcome.w2p en tu nuevo directorio de aplicacin). Los archivos de aplicaciones
tambin se pueden crear y editar desde la lnea de comandos sin necesidad de usar la
interfaz admin.

API
Los modelos, controladores y vistas se ejecutan en un entorno para el cual ya se han
importado por nosotros los siguientes objetos :
Objetos Globales:
request, response, session, cache

Internacionalizacin:
T

Navegacin:
redirect, HTTP

Ayudantes:
XML, URL, BEAUTIFY

A, B, BODY, BR, CENTER, CODE, COL, COLGROUP,


DIV, EM, EMBED, FIELDSET, FORM, H1, H2, H3, H4, H5, H6,
HEAD, HR, HTML, I, IFRAME, IMG, INPUT, LABEL, LEGEND,
LI, LINK, OL, UL, META, OBJECT, OPTION, P, PRE,
SCRIPT, OPTGROUP, SELECT, SPAN, STYLE,
TABLE, TAG, TD, TEXTAREA, TH, THEAD, TBODY, TFOOT,

TITLE, TR, TT, URL, XHTML, xmlescape, embed64

CAT, MARKMIN, MENU, ON

Formularios y tablas
SQLFORM (SQLFORM.factory, SQLFORM.grid, SQLFORM.smartgrid)

Validadores:
CLEANUP, CRYPT, IS_ALPHANUMERIC, IS_DATE_IN_RANGE, IS_DATE,
IS_DATETIME_IN_RANGE, IS_DATETIME, IS_DECIMAL_IN_RANGE,
IS_EMAIL, IS_EMPTY_OR, IS_EXPR, IS_FLOAT_IN_RANGE, IS_IMAGE,
IS_IN_DB, IS_IN_SET, IS_INT_IN_RANGE, IS_IPV4, IS_LENGTH,
IS_LIST_OF, IS_LOWER, IS_MATCH, IS_EQUAL_TO, IS_NOT_EMPTY,
IS_NOT_IN_DB, IS_NULL_OR, IS_SLUG, IS_STRONG, IS_TIME,
IS_UPLOAD_FILENAME, IS_UPPER, IS_URL

Base de datos:
DAL, Field

Para compatibilidad hacia atrs SQLDB=DAL y SQLField=Field . Te recomendamos que uses


la nueva sintaxis DAL y Field , en lugar de la anterior.
Tambin se definen otros objetos y mdulos en las libreras, pero estos no se importan
automticamente, ya que no se usan con tanta frecuencia. Los objetos esenciales de la API en
el
entorno
de
ejecucin
de
web2py
son request , response , session , cache , URL , HTTP , redirect y T y se detallan abajo.
Algunos objetos y funciones, incluyendo Auth, Crud y Service ,
"gluon/tools.py" y se deben importar cuando se los requiere:

estn

definidos

en

from gluon.tools import Auth, Crud, Service

Acceso a la API desde mdulos de Python


Tus mdulos o controladores pueden importar mdulos de Python, y estos pueden necesitar el
uso de alguna parte de la API de web2py. La forma de hacer esto es importando esas partes:

from gluon import *

De hecho, cualquier mdulo de Python, incluso cuando no se importe en el entorno de


ejecucin de web2py, puede importar la API de web2py siempre y cuando web2py est
incluido en el sys.path .
Sin embargo, existe una particularidad. web2py define algunos objetos globales (request,
response, session, cache, T) que slo pueden existir cuando hay una solicitud HTTP
disponible (o simulada). Por lo tanto, los mdulos pueden acceder a ellos slo si se han
llamado desde una aplicacin. Por esta razn se incluyen en un contenedor llamado current ,
que es un objeto que pertenece al dominio de un hilo (thread local). Aqu hay un ejemplo:
Crear un mdulo "/miapp/modules/prueba.py" que contenga:
from gluon import *
def ip(): return current.request.client

Ahora desde un controlador en "miapp" se puede hacer:


import test
def index():
return "Tu ip es " + test.ip()

Algunas cosas a tener en cuenta:


o

import test busca el mdulo inicialmente en la carpeta modules de la app, luego en las

carpetas listadas en sys.path . Por eso, los mdulos del nivel de la aplicacin siempre
tienen precedencia sobre mdulos de Python. Esto permite que distintas app incluyan
distintas versiones de sus mdulos, sin conflictos.
o

Los distintos usuarios pueden llamar a la misma accin index simultneamente, que
llama a la funcin en el mdulo, y sin embargo no hay conflicto
porque current.request es un objeto diferente para distintos hilos. Slo ten cuidado de no
acceder a current.request fuera de funciones o clases (por ejemplo en el nivel ms
general) en el mdulo.

import test es un atajo de from applications.nombreapp.modules import test . Al usar

la sintaxis ms larga, es posible importar mdulos desde otras aplicaciones.


Para mantener la uniformidad con el comportamiento estndar de Python, por defecto web2py
no vuelve a cargar mdulos cuando se realizan cambios. De todos modos esto se puede

cambiar.
Para
habilitar
la
recarga
automtica
de
mdulos,
utiliza
funcin track_changes como sigue (tpicamente en un mdulo, antes de cualquier import):

la

from gluon.custom_import import track_changes; track_changes(True)

De ahora en ms, cada vez que un mdulo se importe, la funcionalidad de importacin


revisar si el archivo de cdigo fuente (.py) ha cambiado. Si se detectan cambios, se cargar
el mdulo nuevamente.

No debes llamar a track_changes en los mdulos en s.


Track changes slo comprueba cambios para mdulos que se almacenan en la aplicacin.
Los mdulos que importan current tienen acceso a:
o

current.request

current.response

current.session

current.cache

current.T

y a cualquier otra variable que tu aplicacin decida almacenar en current. Por ejemplo un
modelo podra hacer esto:
auth = Auth(db)
from gluon import current
current.auth = auth

y ahora todos los mdulos importados tienen acceso a current.auth .


current e import proveen de un poderoso mecanismo para crear mdulos ampliables y

reutilizables para tus aplicaciones.

Hay un detalle importante a tener en cuenta. Dado un from gluon import current , es
correcto el uso de current.request o cualquiera de los dems objetos locales del hilo pero
uno nunca debera pasarlos a variables globales en el mdulo, como en

request = current.request # INCORRECTO! PELIGRO!

ni debera pasarlos a atributos de clase


class MyClass:
request = current.request # INCORRECTO! PELIGRO!

Esto se debe a que los objetos locales del hilo deben extraerse en tiempo de ejecucin.
Las variables globales, en cambio, se definen una sola vez cuando el modelo se importa
inicialmente.
Hay otro problema relacionado con el cach. No se puede usar el objeto cache para decorar
funciones en los mdulos, esto se debe a que el comportamiento no sera el esperado. Para
poder hacer un cach de la funcin f en un mdulo debes usar lazy_cache :
from gluon.cache import lazy_cache

lazy_cache('clave', time_expire=60, cache_model='ram')


def f(a, b, c): ....

Ten en cuenta que la clave est definida por el usuario pero debe estar identificada
estrictamente con la funcin. Si se omite la clave, web2py la determinar automticamente.

request

El objeto request es una instancia de la clase omnipresente llamada gluon.storage.Storage ,


que extiende la clase dict de Python. Bsicamente se trata de un diccionario, pero los valores
de cada tem tambin pueden obtenerse como atributos:
request.vars

es lo mismo que:
request['vars']

A diferencia de un diccionario, si un atributo (o clave) no existe, Storage no genera una


excepcin: en su lugar devuelve None .

A veces es de utilidad crear nuestros propios objetos Storage. Puedes hacerlo de la


siguiente forma:

from gluon.storage import Storage


mi_storage = Storage() # objeto Storage vaco
mi_otro_storage = Storage(dict(a=1, b=2)) # convertir un diccionario a Storage
request tiene los siguientes tems/atributos, de los cuales algunos son tambin instancias de

la clase Storage :
o

request.cookies : un objeto Cookie.SimpleCookie() que contiene las cookie pasadas

con la solicitud HTTP. Se comporta como un diccionario compuesto por cookie. Cada
cookie es un objeto Morsel[morsel].
o

request.env : un objeto Storage que contiene las variables de entorno pasadas al

controlador, incluyendo las variables del encabezado HTTP de la solicitud y los


parmetros WSGI estndar. Las variables de entorno se convierten a minsculas, y los
puntos se convierten a subguiones para mejorar la memorizacin.
o

request.application : el nombre de la aplicacin solicitada.

request.controller : el nombre del controlador solicitado.

request.function : el nombre de la funcin solicitada.

request.extension : la extensin de la accin solicitada. Por defecto es "html". Si la

funcin del controlador devuelve un diccionario y no especifica una vista, esto es usado
para determinar la extensin del archivo de la vista que convertir (render) el diccionario
(extrada de request.env.path_info ).
o

request.folder : el directorio de la aplicacin. Por ejemplo si la aplicacin es

"welcome", request.folder se establece como la ruta absoluta "ruta/a/welcome". En tus


programas, deberas usar siempre esta variable y la funcin os.path.join para obtener
rutas a los archivos que quieras manipular. Si bien web2py usa siempre rutas absolutas,
es una buena prctica no cambiar explcitamente el directorio en uso (current working
directory) sea cual sea, ya que no es una prctica segura para el trabajo con hilos
(thread-safe).
o

request.now : un objeto datetime.datetime que almacena la hora y la fecha de la

solicitud actual.
o

request.utcnow : un objeto datetime.datetime que almacena la hora y fecha UTC de la

solicitud actual.

request.args : Una lista de los componentes de la ruta de la URL que siguen despus

del nombre de la funcin del controlador; equivalente a request.env.path_info.split('/')[3:]


o

request.vars : un objeto gluon.storage.Storage que contiene las variables de la

consulta para HTTP GET y HTTP POST.


o

request.get_vars : un objeto gluon.storage.Storage que contiene slo las variables de

la consulta para HTTP GET.


o

request.post_vars : un objeto gluon.storage.Storage que contiene slo las variables de

la consulta para HTTP POST.


o

request.client :

La

direccin

ip

del

cliente

determinada

por,

si

se

detect, request.env.http_x_forwarded_for o por request.env.remote_addr de lo contrario.


Si bien esto es til no es confiable porque el http_x_forwarded_for se puede falsificar.
o

request.is_local : True si el cliente est en localhost, False en su defecto. Debera de

funcionar detrs de un proxy si el proxy soporta http_x_forwarded_for .


o

request.is_https : True si la solicitud utiliza el protocolo HTTPS, False en su defecto.

request.body : un stream de archivo de slo-lectura conteniendo el cuerpo de la

solicitud
HTTP.
Esto
se
lee
(parse)
automticamente
para
obtener
el request.post_vars para luego devolverse a su estado inicial. Se puede leer
con request.body.read() .
o

request.ajax es True si la funcin se llam desde una solicitud tipo Ajax.

request.cid es el id del componente que gener la solicitud Ajax (en caso de existir).

Puedes leer ms acerca de componentes en el Captulo 12.


o

request.requires_https() evita que se ejecute todo comando si la solicitud no se realiz

utilizando HTTPS y redirige al visitante a la actual pgina usando ese protocolo.


o

request.restful este es un decorador nuevo y realmente til que se puede usar para

cambiar el comportamiento por defecto de una accin de web2py separando las


solicitudes segn GET/POST/PUSH/DELETE. Se tratar con cierto detalle en el Captulo
10.

request.user_agent() extrae (parse) el campo user_agent del cliente y devuelve la

informacin en forma de diccionario. Es til para la deteccin de dispositivos mviles.


Utiliza "gluon/contrib/user_agent_parser.py" creado por Ross Peoples. Para ver como
funciona, prueba incrustando el siguiente cdigo en una vista:
{{=BEAUTIFY(request.user_agent())}}

request.global_settings contiene parmetros de configuracin general de web2py.

Estos parmetros se establecen automticamente y no deberas cambiarlos. Por


ejemplo request.global_settings.gluon_parent contiene la ruta completa a la carpeta de
web2py, request.global_settings.is_pypy determina si web2py est corriendo en PyPy.
o

request.wsgi es un hook que te permite llamar a aplicaciones WSGI de terceros en el

interior de las acciones


El ltimo incluye:
o

request.wsgi.environ

request.wsgi.start_response

request.wsgi.middleware

su uso se trata al final de este Captulo.


Como ejemplo, la siguiente llamada en un sistema tpico:
http://127.0.0.1:8000/examples/default/status/x/y/z?p=1&q=2

resulta en el siguiente objeto request :

variable

valor

request.application

examples

request.controller

default

request.function

index

request.extension

html

request.view

status

request.folder

applications/examples/

request.args

['x', 'y', 'z']

request.vars

<Storage {'p': 1, 'q': 2}>

request.get_vars

<Storage {'p': 1, 'q': 2}>

request.post_vars

<Storage {}>

request.is_local

False

request.is_https

False

request.ajax

False

request.cid

None

request.wsgi

<hook>

request.env.content_length

request.env.content_type

request.env.http_accept

text/xml,text/html;

request.env.http_accept_encoding

gzip, deflate

request.env.http_accept_language

en

request.env.http_cookie

session_id_examples=127.0.0.1.119725

request.env.http_host

127.0.0.1:8000

request.env.http_referer

http://web2py.com/

request.env.http_user_agent

Mozilla/5.0

request.env.path_info

/examples/simple_examples/status

request.env.query_string

remote_addr:127.0.0.1

request.env.request_method

GET

request.env.script_name

request.env.server_name

127.0.0.1

request.env.server_port

8000

request.env.server_protocol

HTTP/1.1

request.env.server_software

Rocket 1.2.6

request.env.web2py_path

/Users/mdipierro/web2py

request.env.web2py_version

Version 2.4.1

request.env.wsgi_errors

<open file, mode 'w' at >

request.env.wsgi_input

request.env.wsgi_url_scheme

http

Segn el servidor web, se establecern unas u otras de las variables de entorno. Aqu nos
basamos en el servidor wsgi incorporado Rocket. El conjunto de variables no difiere en mucho
cuando se utiliza el servidor web Apache.
Las variables de request.env.http_* se extraen del encabezado HTTP de la solicitud.
Las variables de request.env.web2py_* no se extraen del entorno del servidor web, sino que
son creadas por web2py en caso de que la aplicacin necesite saber acerca de la versin y
ubicacin de web2py, y si est corriendo en el Google App Engine (porque algunas
optimizaciones especficas podran ser necesarias).
Se deben tener en cuenta adems las variables de request.env.wsgi_* , que son especficas
del adaptador wsgi.

response
response es otra instancia de la clase Storage , que contiene lo siguiente:

response.body : Un objeto StringIO en el que web2py escribe el cuerpo de la pgina

devuelta. NUNCA MODIFIQUES ESTA VARIABLE.


o

response.cookies : es similar a request.cookies , pero mientras el ltimo contiene las

cookie enviadas desde el cliente al servidor, el primero contiene las cookie enviados
desde el servidor al cliente. La cookie de la sesin se maneja automticamente.
o

response.download(request, db) : un mtodo usado para implementar la funcin del

controlador que permite descargar los archivos subidos. request.download usa el ltimo
argumento en request.args para recuperar el nombre codificado del archivo (por
ejemplo, el nombre del archivo generado cuando se subi al servidor y almacenado en el
campo upload). Este mtodo extrae el nombre del campo upload y el nombre de la tabla
as como tambin el nombre del archivo original del nombre de archivo
codificado. response.dowload recibe dos argumentos opcionales: chunk_size configura el
tamao en byte para streaming por partes (chunked streaming, por defecto es 64K),
y attachments determina si el archivo descargado debera tratarse como attachment o
no (por defecto True ). Ten en cuenta que response.download se usa especficamente
para la descarga de archivos asociados a campos upload de la base de datos.
Usa response.stream (ver abajo) para otras clases de descargas de archivos y
streaming.
Adems,
ten
en
cuenta
que
no
es
necesario
el
uso
de response.download para examinar los archivos subidos a la carpeta static -- los

archivos estticos pueden (y deberan en general) examinarse directamente a travs de


su URL (por ejemplo, /app/static/files/miarchivo.pdf).
o

response.files : una lista de archivos .css, .js, .coffee, y .less asociados a la pgina. Se

aadirn automticamente en el encabezado de la plantilla general "layout.html" a travs


de la vista incluida "web2py_ajax.html". Para aadir nuevos archivos CSS, JS, COFFEE,
o LESS, basta con agregarlos a la lista. Se detectan los archivos duplicados. El orden es
relevante.
o

response.include_files() genera etiquetas del encabezado html para incluir todos los

archivos en response.files (utilizado por "views/web2py_ajax.html").


o

response.flash : parmetro opcional que puede incluirse en las vistas. Normalmente se

usa para notificar al usuario sobre algo que ha ocurrido.


o

response.headers : un dict para los encabezados de la respuesta HTTP. web2py

establece algunos encabezados por defecto, incluyendo "Content-Length", "ContentType", y "X-Powered-By" (que se especifica como web2py). Adems, web2py establece el
valor de los encabezados "Cache-Control", "Expires", y "Pragma" para prevenir el
cacheado del lado del cliente, excepto para las solicitudes de archivos estticos, para los
cuales la opcin de cacheado se habilita. Los encabezados que web2py establece se
pueden sobrescribir o eliminar, y es posible aadir nuevos encabezados (por
ejemplo, response.headers['Cache-Control'] = 'private' ). Puedes eliminar un encabezado
por

su

clave

en

el

diccionario

response.headers,

por

ejemplo

con del

response.headers['Custom-Header'] , sin embargo, los encabezados por defecto de

web2py se agregarn nuevamente antes de devolver la respuesta. Para evitar este


comportamiento, debes establecer el valor del encabezado como None, por ejemplo,
para eliminar el encabezado Content-Type por defecto, usa response.headers['ContentType'] = None

response.menu : parmetro opcional que se puede incluir en las vistas, normalmente

para pasar un rbol de mens de navegacin a la vista. Esto puede ser convertido
(render) por el ayudante MENU.
o

response.meta : un objeto Storage (similar a un diccionario) que contiene informacin

de tipo <meta> opcional como response.meta.author , .description , y/o .keywords . El


contenido de cada variable meta se inserta automticamente en la
etiqueta META correspondiente a travs del cdigo en "views/web2py_ajax.html", que se
incluye en "views/layout.html".

response.include_meta() genera

una

cadena

que

incluye

todos

los

encabezados response.meta serializados (usado por "views/web2py_ajax.html").


o

response.postprocessing : esta es una lista de funciones, vacas por defecto. Estas

funciones se usan para filtrar el objeto response en la salida de una accin, antes de que
la salida sea convertida (render) por la vista. Se podra utilizar para implementar el
soporte de otros lenguajes de plantillas.
o

response.render(vistas, variables) : un mtodo usado para llamar a la vista en forma

explcita en el controlador. vista es un parmetro opcional que especifica el nombre del


archivo de la vista, variables es un diccionario de valores asignados a nombres que se
pasan a la vista.
o

response.session_file : stream de archivo que contiene la sesin.

response.session_file_name : el nombre del archivo donde se guardar la sesin.

response.session_id : el id de la sesin actual. Se detecta automticamente. NUNCA

CAMBIES ESTA VARIABLE.


o

response.session_id_name : el nombre de la cookie de sesin para la app actual.

NUNCA CAMBIES ESTA VARIABLE.


o

response.status : el nmero entero del cdigo de status HTTP que se pasa en la

respuesta. Por defecto es 200 (OK).


o

response.stream(archivo,

chunk_size,

request=request,

attachment=False,

filename=None, headers=None) : cuando un controlador devuelve este objeto, web2py

crea un stream con el contenido para el cliente en bloques del tamao especificado
en chunk_size . El parmetro request es obligatorio para utilizar el inicio del paquete en
el encabezado HTTP. Como se seala ms arriba, response.download debera usarse
para recuperar archivos almacenados a travs del campo upload. Para otros casos se
puede usar response.stream , como el envo de un archivo temporario u objeto StringIO
creado en el controlador.
Si attachment es True, el encabezado Content-Disposition se establecer como "attachment",
y si se pasa el nombre del archivo, tambin se agregar a ese encabezado (pero slo
cuando attachment sea True). Si no se incluyen previamente en response.headers , los
siguientes encabezados de la respuesta se establecern automticamente: Content-Type,
Content-Length, Cache-Control, Pragma y Last-Modified (los ltimos tres se establecen para
permitir el cach del archivo en el navegador). Para sobrescribir cualquiera de estos

encabezados automticos, simplemente configralos en response.headers antes de llamar


a response.stream .
o

response.subtitle : parmetro opcional que se puede incluir en las vistas. Debera

contener el subttulo de la pgina.


o

response.title : parmetro opcional que se puede incluir en las vistas. Debera contener

el ttulo de la pgina y debera ser convertido (render) para el objeto HTML TAG del ttulo
en el encabezado.
o

response.toolbar : una funcin que te permite embeber una barra de herramientas en la

pgina para depuracin {{=response.toolbar()}} . La barra de herramientas muestra las


variables de request, response, session y el tiempo de acceso a la base de datos para
cada consulta.
o

response._vars : se puede acceder a esta variable solamente desde una vista, no en la

accin. Contiene los valores devueltos por la accin a la vista.


o

response._caller : esta es una funcin que envuelve todas las llamadas de la accin.

Por defecto es la funcin idntica, pero se puede modificar para poder manejar ciertas
clases de excepcin y registrar informacin adicional; response._caller = lambda f: f()
o

response.optimize_css :

se

puede

establecer

como

"concat,minify,inline"

para

concatenar, simplificar y alinear los archivos CSS incluidos con web2py.


o

response.optimize_js : se puede establecer como "concat,minify,inline" para concatenar,

simplificar y alinear los archivos JavaScript incluidos con web2py.


o

response.view : el nombre de la plantilla que debe convertir (render) la pgina. Por

defecto es:
"%s/%s.%s" % (request.controller, request.function, request.extension)

o, si este archivo no se encuentra:


"generic.%s" % (request.extension)

Cambia el valor de esta variable para modificar el archivo la vista asociado a una accin
particular.
o

response.delimiters por defecto ('{{','}}') . Te permite cambiar los delimitadores de

cdigo incrustado en las vistas.

response.xmlrpc(request, methods) : si un controlador devuelve este tipo de objeto, la

funcin expone los mtodos a travs de XML-RPC[xmlrpc]. Esta funcin es obsoleta ya que
se ha implementado un mecanismo mejor y se detalla en el Captulo 10.
o

response.write(text) : un mtodo para escribir texto en el cuerpo de la pgina de la

salida.
o

response.js puede contener cdigo JavaScript. Este cdigo se ejecutar si y slo si la

respuesta es recibida por un componente de web2py, segn se detalla en el captulo 12.


Como response es un objeto gluon.storage.Storage , se puede usar para almacenar otros
atributos que quieras pasar a la vista. Si bien no hay una restriccin tcnicamente, lo
recomendable es almacenar slo las variables que se vayan a convertir (render) en todas las
pginas en la plantilla general ("layout.html").
De todos modos, es muy recomendable que el uso est restringido a las variables que se
listan aqu:
response.title
response.subtitle
response.flash
response.menu
response.meta.author
response.meta.description
response.meta.keywords
response.meta.*

porque esto har mucho ms fcil la tarea de reemplazar el archivo "layout.html" que viene
con web2py por otra plantilla, una que use las mismas variables.
Las

versiones

antiguas

de

web2py

usaban response.author en

lugar

de response.meta.author y un formato similar para el resto de los atributos meta.

session
session es otra instancia de la clase Storage . Se puede almacenar cualquier cosa en ella,

por ejemplo:

session.myvariable = "hola"

se puede recuperar ms tarde:


a = session.mivariable

Siempre que el cdigo se ejecute durante la misma sesin para el mismo usuario (suponiendo
que el usuario no elimin las cookie de la sesin y la sesin no venci). Al ser session un
objeto Storage , el intento fallido de acceder a atributos o nombres no establecidos no genera
una excepcin: en su lugar devuelve None .
El objeto session tiene tres mtodos importantes. Uno es forget :
session.forget(response)

Este le dice a web2py que no guarde la sesin. Este mtodo debera usarse en los
controladores cuyas acciones se llamen a menudo y no requieran el registro de la actividad del
usuario. session.forget() impide la escritura del archivo session, sin importar si se ha
modificado o no. session.forget(response) adicionalmente desbloquea y cierra el archivo de la
sesin. Difcilmente necesites llamar a este mtodo ya que las sesiones no se guardan cuando
no han cambiado. Sin embargo, si la pgina hace mltiples solicitudes Ajax simultaneas, es
buena idea que las acciones llamadas va Ajax utilicen session.forget(response) (siempre que
la accin no necesite la sesin). De lo contrario, cada accin Ajax tendr que esperar a la
anterior a que se complete (y a que el archivo de la sesin se desbloquee) antes de continuar,
hacindose ms lenta la descarga de la pgina. Ten en cuenta que las sesiones no se
bloquean cuando se almacenan en la base de datos.
Otro mtodo es:
session.secure()

que le dice a web2py que establezca la cookie de la sesin para que sea segura. Esto se
debera configurar si la app corre sobre https. Al configurar la cookie de sesin como segura,
el servidor le informa al navegador que no enve la cookie de regreso al servidor a menos que
la conexin sea sobre https.
El otro mtodo es connect . Por defecto las sesiones se almacenan en el sistema de archivos
y la cookie de la sesin se usa para almacenar y recuperar el session.id . Usando el
mtodo connect es posible decirle a web2py que almacene las sesiones en la base de datos
o en las cookie, eliminando de esa forma la necesidad de usar el sistema de archivos para el
manejo de las sesiones.

Por ejemplo, para guardar las sesiones en la base de datos:


session.connect(request, response, db, masterapp=None)

donde db es el nombre de una conexin a base de datos abierta (como las que genera la
DAL). Esto le dice a web2py que queremos almacenar las sesiones en la base de datos y no
en el sistema de archivos. session.connect se debe ubicar luego de db=DAL(...) , pero antes
que cualquier otro algoritmo que utilice la sesin, por ejemplo, la configuracin inicial de Auth .
web2py crea una tabla:
db.define_table('web2py_session',
Field('locked', 'boolean', default=False),
Field('client_ip'),
Field('created_datetime', 'datetime', default=now),
Field('modified_datetime', 'datetime'),
Field('unique_key'),
Field('session_data', 'text'))

y almacena una sesin cPickleada en el campo session_data .


La opcin masterapp=None , por defecto, le dice a web2py que intente recuperar una sesin
existente para la aplicacin con el nombre en request.application , en la aplicacin actual.
Si deseas que una o ms aplicaciones compartan las sesiones, establece el valor
de masterapp con el nombre de la aplicacin maestra.
Para almacenar sesiones en cookie en cambio puedes hacer:
session.connect(request, response, cookie_key='yoursecret', compression_level=None)

Aqu cookie_key es

una

clave

de

cifrado

simtrico

(symmetric

encryption

key). compression_level es un nivel de cifrado zlib opcional.


Si bien las sesiones en las cookie son frecuentemente recomendables por razones de
escalabilidad, son limitados en tamao. Las sesiones pesadas producirn fallas en las cookie.

Puedes revisar el estado de tu aplicacin en todo momento mostrando la salida de las


variables del sistema request , session y response . Una forma de hacer esto es creando una
accin especial:
def status():
return dict(request=request, session=session, response=response)

En la vista "generic.html" esto se puede hacer usando {{=response.toolbar()}} .

Separando sesiones
Si almacenas las sesiones en sistemas de archivos y manejas una cantidad importante, el
sistema de archivos puede convertirse un cuello de botella, una forma de resolver esto es la
siguiente:
session.connect(request, response, separate=True)

Al establecer separate=True web2py almacenar las sesiones no en la carpeta sessions/ sino


en distintas subcarpetas de esa ruta. Cada subcarpeta se crear automticamente. Las
sesiones con el mismo prefijo se ubicarn en la misma carpeta. Nuevamente, ten en cuenta
que esto se debe ejecutar antes de cualquier otro algoritmo que utilice el objeto session.

cache
cache es un objeto global que tambin est disponible en el entorno de ejecucin de web2py.

Tiene dos atributos:


o

cache.ram : el cach de la aplicacin en la memoria principal.

cache.disk : el cach de la aplicacin en el disco.

se pueden hacer llamadas a cache (es un callable), esto le permite ser usado como
decorador para el cach de acciones y vistas.
El siguiente ejemplo guarda en cach la funcin time.ctime() en la RAM:
def cache_en_ram():
import time
t = cache.ram('tiempo', lambda: time.ctime(), time_expire=5)

return dict(tiempo=t, link=A('clic aqu', _href=request.url))

La salida de lambda: time.ctime() se guarda en cach en RAM por 5 segundos. La


cadena 'tiempo' se usa como clave del cach.
El ejemplo siguiente guarda en cach la funcin time.ctime() en disco:
def cache_en_disco():
import time
t = cache.disk('tiempo', lambda: time.ctime(), time_expire=5)
return dict(tiempo=t, link=A('clic aqu', _href=request.url))

La salida de lambda: time.ctime() se guarda en cach en el disco (usando el mdulo shelve)


por 5 segundos.
Ten en cuenta que el segundo argumento de cache.ram y cache.disk debe ser una funcin u
objeto que admita llamadas (callable). Si quieres guardar en cach un objeto existente en
lugar de la salida de una funcin, puedes simplemente devolverlo por medio de una funcin
lambda:
cache.ram('miobjeto', lambda: miobjeto, time_expire=60*60*24)

El prximo ejemplo guarda en cach la funcin time.ctime() tanto en RAM como en el disco:
def cache_en_ram_y_disco():
import time
t = cache.ram('tiempo', lambda: cache.disk('tiempo',
lambda: time.ctime(), time_expire=5),
time_expire=5)
return dict(tiempo=t, link=A('clic aqu', _href=request.url))

La salida de lambda: time.ctime() se guarda en cach en el disco (usando el mdulo shelve)


y luego en RAM por 5 segundos. web2py busca en el RAM primero y si no est all busca en
el disco. Si no est en RAM o en el disco, lambda: time.ctime() se ejecuta y se actualiza el
cach. Esta tcnica es de utilidad en un entorno de procesos mltiples (multiprocess). Los dos
objetos tiempo no necesariamente deben ser iguales.

El siguiente ejemplo guarda en cach en RAM la salida de la funcin del controlador (pero no
la vista):
@cache(request.env.path_info, time_expire=5, cache_model=cache.ram)
def cache_del_controlador_en_ram():
import time
t = time.ctime()
return dict(tiempo=t, link=A('clic aqu', _href=request.url))

El diccionario devuelto por cache_del_controlador_en_ram se guarda en cach durante 5


segundos. Ten en cuenta que el resultado de un select de la base de datos no se puede
guardar en cach sin una serializacin previa. Una forma ms apropiada es guardar el select
de la base de datos directamente en cach por medio del argumento chache del
mtodo select .
El siguiente ejemplo guarda en cach la salida de la funcin del controlador en el disco (pero
no la vista):
@cache(request.env.path_info, time_expire=5, cache_model=cache.disk)
def cache_del_controlador_en_disco():
import time
t = time.ctime()
return dict(tiempo=t, link=A('clic para refrescar',
_href=request.url))

El diccionario devuelto por cache_del_controlador_en_disco se guarda en cach en el disco


por 5 segundos. Recuerda que web2py no puede guardar en cach un diccionario que
contenga objetos que no se puedan picklear.
Adems es posible guardar la vista en el cach. El truco consiste en convertir (render) la vista
en la funcin del controlador, para que el controlador devuelva una cadena. Esto se hace
devolviendo response.render(d) , donde d es el diccionario que queremos pasar a la vista. El
siguiente ejemplo guarda en cach la salida de la funcin del controlador en RAM (incluyendo
la vista convertida):
@cache(request.env.path_info, time_expire=5, cache_model=cache.ram)
def cache_de_controlador_y_vista():

import time
t = time.ctime()
d = dict(time=t, link=A('Clic para refrescar', _href=request.url))
return response.render(d)
response.render(d) devuelve la vista convertida como cadena, que ahora se guarda en cach

por 5 segundos. Esta es la mejor y la ms rpida forma de usar el cach.


Ten en cuenta que time_expire se usa para comparar la hora actual con la hora en la que el
objeto solicitado fue almacenado en cach por ltima vez. No afecta a las solicitudes
posteriores. Esto permite a time_expire establecerse dinmicamente cuando se solicita un
objeto en lugar en lugar de tomar un valor fijo cuando se guarda el objeto. Por ejemplo:
mensaje = cache.ram('mensaje', lambda: 'Hola', time_expire=5)

Ahora, supongamos que la siguiente llamada se hace 10 segundos despus de la llamada de


arriba:
mensaje = cache.ram('mensaje', lambda: 'Adis', time_expire=20)

Como time_expire se establece en 20 segundos en la segunda llamada y slo han


transcurrido 10 segundos desde la primera vez que se ha guardado el mensaje, se recuperar
el valor "Hola" de el cach, y no se actualizar con "Adis". El valor de time_expire de 5
segundos en la primera llamada no tiene impacto en la segunda llamada.
Al configurar time_expire=0 (o usando un valor negativo), se fuerza la actualizacin del tem
en cach (porque el tiempo transcurrido desde el ltimo almacenamiento ser siempre > 0), y
si se configura time_expire=None se fuerza la recuperacin del valor en cach, sin importar el
tiempo transcurrido desde la ltima vez que se guard (si time_expire es siempre None , se
impide efectivamente el vencimiento del tem en cach).
Puedes borrar una o ms variables de cach con
cache.ram.clear(regex='...')

donde regex es una expresin regular (regular expression) que especifica todas las palabras
que quieras eliminar del cach. Tambin puedes eliminar un slo tem con:
cache.ram(clave, None)

donde clave es la palabra asociada al tem en cach.

Adems es posible definir otros mecanismos de cach como memcache. Memcache est
disponible con gluon.contrib.memcache y se trata con ms detalle en el Captulo 14.

Ten cuidado con el cach porque usualmente trabaja en el nivel de la aplicacin, no en el


nivel de usuario. Si necesitas, por ejemplo, guardar en cach contenido especfico del
usuario, utiliza una clave que incluya el id de ese usuario.

URL

La funcin URL es una de las ms importantes de web2py. Genera URL de rutas internas
para las acciones y los archivos estticos.
Aqu hay un ejemplo:
URL('f')

se asocia (map) a
/[aplicacin]/[controlador]/f

Ten en cuenta que la salida de la funcin URL depende del nombre de la aplicacin actual, el
controlador que se llam y otros parmetros. web2py soporta URL mapping y URL mapping
inverso. El URL mapping o mapeo de URL te permite redefinir el formato de las URL externas.
Si usas la funcin URL para generar todas las URL internas, entonces los agregados o
modificaciones no presentarn vnculos incorrectos (broken links) en el mbito de la
aplicacin.
Puedes pasar parmetros adicionales a la funcin URL , por ejemplo, palabras extra en la ruta
del URL (args) y variables de consulta (query variables):
URL('f', args=['x', 'y'], vars=dict(z='t'))

se asocia (mapea) a
/[aplicacin]/[controlador]/f/x/y?z=t

Los

atributos arg son

ledos

(parse),

decodificados

finalmente

almacenados

automticamente en request.args por web2py. De forma similar ocurre con las variables de
consulta que se almacenan en request.vars . args y vars proveen de un mecanismo bsico
usado por web2py para el intercambio de informacin con el navegador cliente.
Si args contiene slo un elemento, no hace falta que se pase como lista.

Adems puedes usar la funcin URL para generar las URL de acciones en otros
controladores o aplicaciones:
URL('a', 'c', 'f', args=['x', 'y'], vars=dict(z='t'))

se asocia (map) a
/a/c/f/x/y?z=t

Adems es posible especificar una aplicacin, controlador y funcin usando argumentos con
nombre (named arguments):
URL(a='a', c='c', f='f')

Si no se especifica el nombre de la aplicacin se asume la app actual.


URL('c', 'f')

Si falta el nombre del controlador, se asume el actual.


URL('f')

En lugar de pasar el nombre de una funcin del controlador tambin es posible pasar la
funcin en s
URL(f)

Por las razones expuestas ms arriba, deberas utilizar siempre la funcin URL para generar
los URL de archivos estticos para tus aplicaciones. Los archivos estticos se almacena en la
subcarpeta static de la aplicacin (es ese el lugar que se les asigna cuando se suben a
travs de la interfaz administrativa). web2py provee de un controlador virtual 'static' que tiene
la tarea de recuperar los archivos de la subcarpeta static , determinar su tipo de contenido, y
crear el stream del archivo para el cliente. El siguiente ejemplo genera una URL para la
imagen esttica "imagen.png":
URL('static', 'imagen.png')

se asocia (map) a
/[aplicacin]/static/imagen.png

Si la imagen esttica est en una subcarpeta incluida en la carpeta static , puedes incluir la/s
subcarpeta/s como parte del nombre del archivo. Por ejemplo, para generar:

/[aplicacin]/static/imagenes/iconos/flecha.png

uno debera usar:


URL('static', 'imagenes/iconos/flecha.png')

No es necesario que codifiques o escapes los argumentos en args o vars ; esto se realiza
automticamente por ti.
Por defecto, la extensin correspondiente a la solicitud actual (que se puede encontrar
en request.extension ) se agrega a la funcin, a menos que request.extension sea html, el
valor por defecto. Este comportamiento se puede sobrescribir incluyendo explcitamente una
extensin como parte del nombre de la funcin URL(f='nombre.ext') o con el argumento
extension:
URL(..., extension='css')

La extensin actual se puede omitir explcitamente:


URL(..., extension=False)

URL absolutos
Por defecto, URL genera URL relativas. Sin embargo, puedes adems generar URL absolutas
especificando los argumentos scheme y host (esto es de utilidad, por ejemplo, cuando se
insertan URL en mensajes de email):
URL(..., scheme='http', host='www.misitio.com')

Puedes incluir automticamente el scheme y host de la solicitud actual simplemente


estableciendo los argumentos como True .
URL(..., scheme=True, host=True)

La funcin URL adems acepta un argumento port para especificar el puerto del servidor si
es necesario.

Firma digital de URL


Cuando generas una URL, tienes la opcin de firmarlas digitalmente. Esto aadir una
variable _signature tipo GET que se puede ser verificada por el servidor. Esto se puede
realizar de dos formas distintas.

Puedes pasar los siguientes argumentos a la funcin URL:


o

hmac_key : la clave para la firma del URL (una cadena)

salt : una cadena opcional para utilizar la tcnica salt antes de la firma

hash_vars : una lista opcional de nombres de variables de la cadena de la consulta

URL (query string variables, es decir, variables GET) a incluir en la firma. Tambin se
puede establecer como True (por defecto) para incluir todas las variables, o False para
no incluir variables.
Aqu se muestra un ejemplo de uso:
KEY = 'miclave'

def uno():
return dict(link=URL('dos', vars=dict(a=123), hmac_key=KEY))

def dos():
if not URL.verify(request, hmac_key=KEY): raise HTTP(403)
# hacer algo
return locals()

Esto hace que se pueda acceder a la accin dos slo por medio de una URL firmada
digitalmente. Una URL firmada digitalmente se ve as:
'/welcome/default/dos?a=123&_signature=4981bc70e13866bb60e52a09073560ae822224e9'

Ten
en
cuenta
que
la
firma
funcin URL.verify . URL.verify adems

digital
toma

se
verifica
a
travs
de
la
los
parmetros hmac_key , salt ,

y hash_vars descriptos anteriormente, y sus valores deben coincidir con los que se pasaron a
la funcin URL cuando se cre la firma digital para poder verificar la URL.
Una segunda forma ms sofisticada y ms usual de URL firmadas digitalmente es la
combinacin con Auth. Esto se explica ms fcilmente por medio de un ejemplo:
@auth.requires_login()
def uno():

return dict(link=URL('dos', vars=dict(a=123), user_signature=True)

@auth.requires_signature()
def dos():
# hacer algo
return locals()

En este caso la hmac_key se genera automticamente y se comparte en la sesin. Esto


permite que la accin dos delegue todo control de acceso a la accin uno . Si se genera el
link y se firma, este es vlido; de lo contrario no lo es. Si otro usuario se apropia del link, este
no ser vlido.
Es una buena prctica la firma digital de todo callback de Ajax. Si usas la funcin LOAD , esta
tambin tiene un argumento user_signature que se puede usar con ese fin:
{{=LOAD('default', 'dos', vars=dict(a=123), ajax=True, user_signature=True)}}

HTTP

and redirect

web2py define slo una excepcin llamada HTTP . Esta excepcin se puede generar en
cualquier parte de un modelo, controlador o vista con el comando:
raise HTTP(400, "mi mensaje")

Esto hace que el flujo del control (control flow) se salga del cdigo del usuario, de vuelta a
web2py y que devuelva una respuesta HTTP como esta:
HTTP/1.1 400 BAD REQUEST
Date: Sat, 05 Jul 2008 19:36:22 GMT
Server: Rocket WSGI Server
Content-Type: text/html
Via: 1.1 127.0.0.1:8000
Connection: close
Transfer-Encoding: chunked

mi mensaje

El primer argumento de HTTP es el cdigo de estado HTTP. El segundo argumento es la


cadena que se devolver como cuerpo de la respuesta. Se pueden pasar otros argumentos
por nombre adicionales para crear el encabezado de la respuesta HTTP. Por ejemplo:
raise HTTP(400, 'mi mensaje', test='hola')

genera:
HTTP/1.1 400 BAD REQUEST
Date: Sat, 05 Jul 2008 19:36:22 GMT
Server: Rocket WSGI Server
Content-Type: text/html
Via: 1.1 127.0.0.1:8000
Connection: close
Transfer-Encoding: chunked
test: hola

mi mensaje

Si no deseas aplicar los cambios (commit) de la transaccin abierta de la base de datos,


puedes anularlos (rollback) antes de generar la excepcin.
Toda excepcin que no sea HTTP hace que web2py anule (rollback) toda transaccin de base
de datos abierta, registre el error, enve un ticket al visitante y devuelva una pgina de error
estndar.
Esto significa que el flujo de control entre pginas slo es posible con HTTP . Las otras
excepciones se deben manejar en la aplicacin, de lo contrario, web2py generar un ticket.
El comando:
redirect('http://www.web2py.com')

es bsicamente un atajo de:


raise HTTP(303,

'Ests siendo redirigido a esta <a href="%s">pgina web</a>' % ubicacion,


Location='http://www.web2py.com')

Los argumentos por nombre del mtodo de inicializacin HTTP se traducen en directivas de
encabezado HTTP, en este caso, la ubicacin de destino de la redireccin (target
location). redirect toma un segundo argumento opcional, que es el cdigo de estado HTTP
para la redireccin (por defecto 303). Para una redireccin temporaria cambia ese valor a 307
o puedes cambiarlo a 301 para una redireccin permanente.
La forma ms usual para redirigir es la redireccin a otras pginas en la misma app y
(opcionalmente) pasar parmetros:
redirect(URL('index', args=(1,2,3), vars=dict(a='b')))

En el Captulo 12 trataremos sobre los componentes de web2py. Ellos hacen solicitudes Ajax a
acciones de web2py. Si la accin llamada hace un redirect, podras necesitar que la solicitud
Ajax siga la redireccin o que la pgina completa cambie de direccin. Para este ltimo caso,
se puede establecer:
redirect(..., type='auto')

Internacionalizacin y Pluralizacin con T


El objeto T es el traductor de idiomas. Se compone de una nica instancia global de la clase
de web2py gluon.language.translator . Todas las cadenas fijas (string constants, y slo ellas)
deberan marcarse con T , por ejemplo:
a = T("hola mundo")

Las cadenas que se marcan con T son detectadas por web2py como traducibles y se
traducirn cuando el cdigo (en el modelo, controlador o vista) se ejecute. Si la cadena a
traducir no es constante, sino que es variable, se agregar al archivo de traduccin en tiempo
de ejecucin (runtime, salvo en GAE) para su traduccin posterior.
El objeto T tambin admite interpolacin de variables y soporta mltiples sintaxis
equivalentes:
a = T("hola %s", ('Timoteo',))
a = T("hola %(nombre)s", dict(nombre='Timoteo'))
a = T("hola %s") % ('Tim',)

a = T("hola %(nombre)s") % dict(nombre='Timoteo')

La ltima de las sintaxis es la recomendada porque hace la traduccin ms fcil. La primera


cadena se traduce segn el archivo de idioma solicitado y la variable nombre se reemplaza
independientemente del idioma.
Es posible la concatenacin de cadenas traducidas y cadenas normales:
T("bla ") + nombre + T("bla")

El siguiente cdigo tambin est permitido y con frecuencia es preferible:


T("bla %(nombre)s bla", dict(nombre='Timoteo'))

o la sintaxis alternativa
T("bla %(nombre)s bla") % dict(nombre='Timoteo')

En ambos casos la traduccin ocurre antes de que la variable nombre sea sustituida en la
ubicacin de "%(nombre)s". La alternativa siguiente NO SE DEBERA USAR:
T("bla %(nombre)s bla" % dict(nombre='Timoteo'))

porque la traduccin ocurrira despus de la sustitucin.

Estableciendo el idioma
El lenguaje solicitado se determina con el campo "Accepted-Language" en el encabezado
HTTP, pero esta opcin se puede sobrescribir programticamente solicitando un archivo
especfico, por ejemplo:
T.force('it-it')

que lee el archivo de idioma "languages/it-it.py". Los archivos de idiomas se pueden crear y
editar a travs de la interfaz administrativa.
Adems puedes forzar el uso de un idioma para cada cadena:
T("Hola Mundo", language="it-it")

En el caso de que se soliciten mltiples idiomas, por ejemplo "it-it, fr-fr", web2py intentar
ubicar los archivos de traduccin "it-it.py" y "fr-fr.py". Si no se encuentra ninguno de los
archivos, intentar como alternativa "it.py" y "fr.py". Si estos archivos no se encuentran
utilizar "default.py". Si tampoco ese archivo es encontrado, usar el comportamiento por

defecto sin traduccin. La regla, en una forma ms general, es que web2py intenta con
"xx-xy-yy.py", luego "xx-xy.py", luego "xx.py" y por ltimo "default.py" para cada idioma "xxxy-yy" aceptado, buscando la opcin ms parecida a las preferencias del usuario.
Puedes deshabilitar completamente las traducciones con
T.force(None)

Normalmente, las traducciones de cadenas se evalan con pereza (lazily) cuando se convierte
(render) la vista; por lo tanto, no se debera usar force dentro de una vista.
Es posible deshabilitar la evaluacin perezosa con
T.lazy = False

De esta forma, las cadenas se traducen de inmediato con el operador T segn el idioma
establecido o forzado.
Adems se puede deshabilitar la evaluacin perezosa para cadenas individuales:
T("Hola Mundo", lazy=False)

El siguiente es un problema usual. La aplicacin original est en Ingls. Supongamos que


existe un archivo de traduccin (por ejemplo en Italiano, "it-it.py") y el cliente HTTP declara que
acepta tanto Ingls como Italiano en ese orden. Ocurre la siguiente situacin inesperada:
web2py no sabe que por defecto la app se escribi en Ingls (en). Por lo tanto, prefiere
traducir todo al Italiano (it-it) porque nicamente detect el archivo de traduccin al Italiano. Si
no hubiera encontrado el archivo "it-it.py", hubiese usado las cadenas del idioma por defecto
(Ingls).
Hay dos soluciones posibles para este problema: crear el archivo de traduccin al Ingls, que
sera algo redundante e innecesario, o mejor, decirle a web2py qu idiomas deberan usar las
cadenas del idioma por defecto (las cadenas escritas en la aplicacin). Esto se hace con:
T.set_current_languages('en', 'en-en')

Esto almacena una lista de idiomas en T.current_languages que no necesitan traduccin y


fuerza la recarga de los archivos de idiomas.
Ten en cuenta que "it" e "it-it" son idiomas diferentes desde el punto de vista de web2py. Para
dar soporte para ambos, uno necesitara dos archivos de traduccin, siempre en minsculas.
Lo mismo para los dems idiomas.

El idioma aceptado actualmente se almacena en


T.accepted_language

Traduccin de variables
T(...) no slo traduce las cadenas sino que adems puede traducir los valores contenidos en
variables:
>>> a="test"
>>> print T(a)

En este caso se traduce la palabra "test" pero, si no se encuentra y el sistema de archivos


permite la escritura, se agregar a la lista de palabras a traducir en el archivo del idioma.
Observa que esto puede resultar en una gran cantidad de E/S de archivo y puedes necesitar
deshabilitarlo:
T.is_writable = False

evita que T actualice los archivos de idioma en forma dinmica.

Comentarios y traducciones mltiples


Es posible que la misma cadena aparezca en distintos contextos en la aplicacin y que
necesite distintas traducciones segn el contexto. Para resolver este problema, uno puede
agregar comentarios a la cadena original. Los comentarios no se convierten, web2py los usar
para determinar la traduccin ms apropiada. Por ejemplo:
T("hola mundo ## primer caso")
T("hola mundo ## segundo caso")

Los textos a la derecha de los ## , incluyendo los dobles ## , son comentarios.

El motor de pluralizacin
A partir de la versin 2.0, web2py incluye un potente sistema de pluralizacin (PS). Esto quiere
decir que cuando se marque al texto para traduccin y el texto dependa de una variable
numrica, puede ser traducido en forma diferente segn el valor numrico. Por ejemplo en
Ingls podemos convertir:
x book(s)

como
a book (x==1)
5 books (x==5)

El idioma ingls tiene una forma para el singular y una para el plural. La forma plural se
construye agregando una "-s" o "-es" o usando una forma especial. web2py provee de una
forma de definir reglas de pluralizacin para cada lenguaje, as como tambin definir
excepciones a esas reglas. De hecho, web2py maneja por defecto las reglas de varios
idiomas, y tambin excepciones a sus reglas. Sabe, por ejemplo, que el esloveno tiene una
forma singular y 3 formas plurales (para x==1, x==3 o x==4 y x>4). Estas reglas se establecen
en los archivos "gluon/contrib/plural_rules/*.py" y se pueden crear nuevos archivos. Las
pluralizaciones explcitas para las palabras se pueden crear editando los archivos de
pluralizacin en la interfaz administrativa.
Por defecto, el sistema PS no est activado.
argumento symbols de la funcin T . Por ejemplo:

Se

activa

cuando

su

usa

el

T("Tienes %s %%{libro}", symbols=10)

Ahora el sistema PS se activar para la palabra "libro" y el nmero 10. El resultado en Espaol
ser: "Tienes 10 libros". Observa que "libro" se ha pluralizado como "libros".
El sistema PS est compuesto por 3 partes:
o

marcadores de posicin %%{} para marcar las palabras en textos procesados por T

reglas para establecer qu forma se usar ("rules/plural_rules/*.py")

un diccionario de formas plurales de palabras "app/languages/plural-*.py"

El valor de symbols puede ser una nica variable, una lista o tupla de variables o un
diccionario.
El marcador de posicin %%{} est compuesto por 3 partes:
%%{[<modificador>]<palabra>[<parmetro>]},

donde:
<modificador>::= ! | !! | !!!
<palabra> ::= cualquier palabra o frase en singular y minsculas (!)

<parmetro> ::= [ndice] | (clave) | (nmero)

Por ejemplo:
o

%%{palabra} equivale a %%{palabra[0]} (si no se usan modificadores).

%%{palabra[ndice]} se usa cuando symbols es una tupla. symbols[ndice] nos da un

nmero usado para establecer que forma de la palabra se debe usar.


o

%%{palabra(clave)} se usa para recuperar el parmetro numrico en symbols[clave]

%%{palabra(numero)} permite

establecer

un numero en

forma

directa

(por

ejemplo: %%{palabra(%i)} )
o

%%{?palabra?numero} devuelve "palabra" si se comprueba numero==1 , de lo

contrario devuelve numero


o

%%{?numero} or %%{??numero} devuelve numero si numero!=1 , de lo contrario no

devuelve nada
T("blabla %s %%{palabra}", symbols=var)
%%{palabra} por defecto significa %%{word[0]} , donde [0] es un ndice de tem de la tupla

de symbols.
T("blabla %s %s %%{palabra[1]}", (var1, var2))

Se usa el PS para "palabra" y var2 respectivamente.


Puedes usar muchos marcadores de posicin %%{} con el ndice:
T("%%{este} %%{es} %%{el} %s %%{libro}", var)

o
T("%%{este[0]} %%{es[0]} %%{el[0]} %s %%{libro[0]}", var)

Estas expresiones generarn lo siguiente:


var salida
------------------

1 este es el 1 libro
2 estos son los 2 libros
3 estos son los 2 libros

En forma similar puedes pasar un diccionario a los smbolos:


T("blabla %(var1)s %(ctapalabras)s %%{word(ctapalabras)}",
dict(var1="tututu", ctapalabras=20))

que producir
blabla tututu 20 palabras

Puedes reemplazar "1" con cualquier palabra que desees usando el marcador de posicin %
%{?palabra?numero} . Por ejemplo
T("%%{este} %%{es} %%{?un?%s} %%{libro}", var)

produce:
var salida
-----------------1 este es un libro
2 estos son 2 libros
3 estos son 3 libros
...

Dentro de %%{...} puedes adems usar los siguientes modificadores:


o

! para que la letra inicial sea mayscula (equivale a string.capitalize )

!! para que cada palabra tenga inicial mayscula (equivale a string.title )

!!! para convertir cada palabra a maysculas (equivale a string.upper )

Ten en cuenta que puedes usar \ para escapar ! y ? .

Traducciones, pluralizacin y MARKMIN


Tambin puedes usar la potente sintaxis de MARKMIN dentro de las cadenas a traducir
reemplazando
T("hola mundo")

con
T.M("hola mundo")

Ahora la cadena aceptar el lenguaje de marcas MARKMIN segn se describe ms adelante


en este libro. Adems puedes usar el sistema de pluralizacin dentro de MARKMIN.

Cookie
web2py utiliza los mdulos de Python para el manejo de las cookie.
Las cookie del navegador se encuentran en request.cookies y las cookie enviadas por el
servidor estn en response.cookies .
Puedes crear valores de cookie de la siguiente forma:
response.cookies['micookie'] = 'unvalor'
response.cookies['micookie']['expires'] = 24 * 3600
response.cookies['micookie']['path'] = '/'

La segunda linea le dice al navegador que conserve la cookie por 24 horas. La tercer lnea le
dice al navegador que enve una cookie de regreso a cualquier aplicacin en el dominio actual
(ruta URL). Ten en cuenta que si no especificas la ruta para la cookie, el navegador se
configurar con la ruta del URL que se solicit, por lo que la cookie slo se enviar de regreso
al servidor cuando se solicite esa misma ruta URL.
La cookie se puede hacer segura con:
response.cookies['micookie']['secure'] = True

Esto le dice al navegador que slo devuelva la cookie sobre HTTPS, no sobre HTTP.
La cookie se puede recuperar con:

if request.cookies.has_key('micookie'):
valor = request.cookies['micookie'].value

A menos que se deshabiliten las sesiones, web2py, como proceso interno, establece la
siguiente cookie, que usa para el manejo de sesiones:
response.cookies[response.session_id_name] = response.session_id
response.cookies[response.session_id_name]['path'] = "/"

Ten en cuenta que si una aplicacin determinada incluye subdominios mltiples, y quieres
compartir las sesiones a travs de esos subdominios (por ejemplo, sub1.tudominio.com,
sub2.tudominio.com, etc.), debes establecer explcitamente el dominio de la cookie de sesin
de esta forma:
if not request.env.remote_addr in ['127.0.0.1', 'localhost']:
response.cookies[response.session_id_name]['domain'] = ".tudominio.com"

El comando descripto arriba puede ser til si, por ejemplo, quisieras permitir al usuario que
permanezca autenticado para distintos dominios.

La aplicacin init
Cuando despliegues (deploy) web2py, querrs establecer una aplicacin por defecto, por
ejemplo, la aplicacin que se inicia cuando hay una ruta vaca en la URL, como en:
http://127.0.0.1:8000

Por defecto, cuando web2py se encuentra con una ruta vaca, busca una aplicacin
llamada init. Si no hay una aplicacin init buscar una aplicacin llamada welcome.
El nombre de la apliacin por defecto se puede cambiar de init a otro nombre estableciendo el
valor de default_application en routes.py:
default_application = "miapp"

Ten en cuenta que default_application se agreg por primera vez en la versin 1.83 de web2y.
Aqu mostramos cuatro formas de establecer la aplicacin por defecto:
o

Establecer el nombre de la aplicacin por defecto como "init".

Establecer default_application con el nombre de la aplicacin en routes.py

Hacer un link simblico de "applications/init" a la carpeta de la aplicacin.

Usar la reescritura de URL como se detalla en la prxima seccin.

Reescritura de URL
web2py tiene la habilidad de reescribir la ruta de la URL para solicitudes entrantes antes de
llamar a la accin en el controlador (mapeo de URL o URL mapping), y en el otro sentido,
web2py puede reescribir la ruta de la URL generada por la funcin URL (mapeo reverso de
URL o reverse URL mapping). Una de las razones para esto es para el soporte de las URL
heredadas (legacy), otra razn es la posibilidad de simplificar las rutas y hacerlas ms cortas.
web2py incluye dos sistemas de reescritura de URL: un sistema basado en parmetros de fcil
uso para la mayor parte de los casos posibles, y un sistema basado en patrones para los
casos ms complicados. Para especificar las reglas de reescritura de URL, crea un archivo
nuevo en la carpeta "web2py" llamada routes.py (el contenido de routes.py depender del
sistema de reescritura que elijas, como se describe en las siguientes secciones). Los dos
sistemas no se pueden mezclar.

Observa que si editas routes.py, debes volver a cargarlo. Esto se puede hacer en dos
formas: reiniciando el servidor web o haciendo clic en el botn para cargar nuevamente
routes en admin. Si hay un fallo en routes, no se actualizar.

Sistema basado en parmetros


El sistema basado en parmetros (paramtrico) provee de un acceso fcil a muchos mtodos
"enlatados". Entre sus posibilidades se puede enumerar:
o

Omitir los nombres de la aplicacin, controlador y funcin por defecto en las URL
visibles desde afuera (las creadas por la funcin URL())

Asociar (mapear) dominios (y/o puertos) a controladores de aplicaciones

Embeber un selector de idioma en la URL

Eliminar un prefijo determinado de las URL entrantes y aadirlo nuevamente a las URL
salientes

Asociar (mapear) archivos de la raz como /robots.txt a un directorio esttico de la


aplicacin

El router paramtrico tambin provee de una forma ms flexible de validacin para las URL
entrantes.
Digamos que has creado una aplicacin llamada miapp y deseas que sea la aplicacin por
defecto, de forma que el nombre de la aplicacin ya no forme parte de la URL cuando la ve el
usuario. Tu controlador por defecto sigue siendo default , y tambin quieres eliminar su
nombre de la URL que ve el usuario. Esto es entonces lo que debes poner en routes.py :
routers = dict(
BASE = dict(default_application='miapp'),
)

Y eso es todo. El router paramtrico es lo suficientemente inteligente para hacer lo correcto


con una URL como:
http://dominio.com/myapp/default/miapp

o
http://dominio.com/myapp/miapp/index

donde el acortamiento normal sera ambiguo. Si tienes dos aplicaciones, miapp y miapp2 ,
obtendrs el mismo efecto, y adicionalmente, el controlador por defecto de miapp2 se
recortar de la URL cuando sea seguro (que es lo normal casi siempre).
Aqu hay otro caso: digamos que quieres dar soporte para lenguajes basado en URL, donde
las URL son algo as:
http://miapp/es/una/ruta

o (reescrita)
http://es/una/ruta

Esta es la forma de hacerlo:


routers = dict(
BASE = dict(default_application='miapp'),
miapp = dict(languages=['en', 'it', 'jp'], default_language='es'),
)

Ahora una URL entrante como esta:


http:/dominio.com/it/una/ruta

se asociar como /miapp/una/ruta , y se establecer request.uri_language como 'it', para que


puedas forzar la traduccin. Adems puedes tener archivos estticos especficos por idioma.
http://domain.com/it/static/archivo

se asociar con:
applications/miapp/static/it/archivo

si ese archivo existiera. Si no, entonces los URL como:


http://domain.com/it/static/base.css

se asociarn como:
applications/miapp/static/base.css

(porque no hay un static/it/base.css ).


Entonces ahora puedes tener archivos estticos especficos por idioma, incluyendo imgenes,
si as lo necesitaras. Tambin est soportada el mapeo de dominio (domain mapping):
routers = dict(
BASE = dict(
domains = {
'dominio1.com' : 'app1',
'dominio2.com' : 'app2',
}
),
)

cuya funcin es obvia.


routers = dict(
BASE = dict(

domains = {
'dominio.com:80' : 'app/insegura',
'domnio.com:443' : 'app/segura',
}
),
)

asocia los accesos al controlador llamado inseguro de http://dominio.com , mientras que los
accesos con HTTPS van al controlador seguro . Como alternativa, puedes asociar distintos
puertos a distintas app, con una notacin anloga a la de los ejemplos anteriores.
Para ms informacin, puedes consultar el archivo router.example.py que se incluye en la
carpeta raz de la distribucin estndar de web2py.
Nota: el sistema basado en parmetros est disponible desde la versin 1.92.1 de web2py.

Sistema basado en patrones


Si bien el sistema basado en parmetros recin descripto debera de ser suficiente para la
mayor parte de los casos, el sistema alternativo basado en patrones provee de flexibilidad
adicional para casos ms complejos. Para usar el sistema basado en patrones, en lugar de
definir los enrutadores como diccionarios con parmetros de enrutamiento, se definen dos
listas (o tuplas) de pares (2-tuplas), routes_in y routes_out . Cada tupla contiene dos
elementos: el patrn a reemplazar y la cadena que lo reemplaza. Por ejemplo:
routes_in = (
('/pruebame', '/ejemplos/default/index'),
)
routes_out = (
('/ejemplos/default/index', '/pruebame'),
)

Con estas rutas, la URL:


http://127.0.0.1:8000/pruebame

es asociada a:

http://127.0.0.1:8000/ejemplos/default/index

Para el visitante, todos los link a la URL de la pgina se ven como /pruebame .
Los patrones tienen la misma sintaxis que las expresiones regulares de Python. Por ejemplo:
('.*.php', '/init/default/index'),

asocia toda URL que termine en ".php" a la pgina de inicio.


El segundo trmino de una regla tambin puede ser una redireccin a otra pgina:
('.*.php', '303->http://ejemplo.com/nuevapagina'),

Aqu 303 es el cdigo HTTP para la respuesta de redireccin.


A veces queremos deshacernos del prefijo de la aplicacin en las URL porque queremos
exponer slo una aplicacin. Esto es posible con:
routes_in = (
('/(?P<any>.*)', '/init/\g<any>'),
)
routes_out = (
('/init/(?P<any>.*)', '/\g<any>'),
)

Tambin hay una sintaxis alternativa que se puede mezclar con la notacin anterior de
expresiones
regulares.
Consiste
en
usar $nombre en
lugar
de (?
P<nombre>\w+) o \g<nombre> . Por ejemplo:
routes_in = (
('/$c/$f', '/init/$c/$f'),
)

routes_out = (
('/init/$c/$f', '/$c/$f'),
)

tambin elimina el prefijo de la aplicacin en todas las URL.


Si usas la notacin $nombre , puedes asociar automticamente routes_in a routes_out ,
siempre y cuando no uses expresiones regulares. Por ejemplo:
routes_in = (
('/$c/$f', '/init/$c/$f'),
)

routes_out = [(x, y) for (y, x) in routes_in]

Si existen mltiples rutas, se ejecuta la primera que coincida con la URL, si no hay
coincidencias en el patrn, no se hacen cambios a la ruta.
Puedes usar $anything para comparar con cualquier cadena ( .* ) hasta el final de lnea.
Aqu mostramos una versin mnima de "routes.py" para el manejo de solicitudes de favicon y
robot:
routes_in = (
('/favicon.ico', '/ejemplos/static/favicon.ico'),
('/robots.txt', '/ejemplos/static/robots.txt'),
)
routes_out = ()

Este es un ejemplo ms complejo que expone una sola app, "miapp", sin prefijos innecesarios
y que adems expone admin, appadminy static:
routes_in = (
('/admin/$anything', '/admin/$anything'),
('/static/$anything', '/miapp/static/$anything'),
('/appadmin/$anything', '/miapp/appadmin/$anything'),
('/favicon.ico', '/miapp/static/favicon.ico'),
('/robots.txt', '/miapp/static/robots.txt'),
)
routes_out = [(x, y) for (y, x) in routes_in[:-2]]

La sintaxis general para routes es ms compleja que los ejemplos bsicos que hemos visto
hasta ahora. Este es un ejemplo ms general y representativo:
routes_in = (
('140.191.\d+.\d+:https?://www.web2py.com:post /(?P<any>.*).php',
'/prueba/default/index?vars=\g<any>'),
)

Asocia las solicitudes http o https POST (ten en cuenta el uso de minsculas en "post") a la
mquina en www.web2py.com desde una IP remota que coincide con la expresin regular
'140.191.\d+.\d+'

Si se solicita una pgina que coincida con la expresin


'/(?P<any>.*).php'

se asociar a
'/prueba/default/index?vars=\g<any>'

donde \g<any> es reemplazada por la expresin regular que coincida.


La sintaxis general es
'[direccin remota]:[protocolo]://[equipo (host)]:[mtodo] [ruta]'

Si falta la primer seccin del patrn (todo excepto [ruta] , web2py la reemplaza con un valor
por defecto:
'.*?:https?://[^:/]+:[a-z]+'

La expresin completa se compara como expresin regular, por lo que "." debe escaparse
(escape) y toda subexpresin que coincida se puede capturar usando (?P<...>...) , con la
notacin de expresiones regulares de Python. El mtodo de la solicitud (tpicamente GET o
POST) debe ser en minsculas. Adems, se eliminan los delimitadores (unquote) de toda
cadena de tipo %xx en la URL a comparar.
Esto permite reenrutar las solicitudes basadas en la IP o dominio del cliente, segn el tipo de
solicitud, tipo de mtodo y ruta. Adems permite que web2py asocie distintas mquinas

virtuales (virtual host) a distintas aplicaciones. Toda subexpresin que coincida se puede usar
para armar la URL de destino (target) y, eventualmente, puede ser pasada como variable GET.
Los servidores ms conocidos, como Apache y lighttpd, tienen adems la posibilidad de
reescribir las URL. En un entorno de produccin se podra optar por esa opcin en lugar
de routes.py . Sea cual sea tu decisin, te recomendamos que no escribas "a mano"
(hardcode) las URL internas en tu app y que uses la funcin URL para generarlas. Esto har
tu aplicacin ms porttil en caso de que se realicen cambios en el enrutamiento.

Reescritura de URL especfica de una aplicacin


Al usar el sistema basado en patrones, una aplicacin puede configurar sus propias rutas en
un archivo especfico routes.py ubicado en la carpeta base de la aplicacin. Esto se habilita
configurando routes.py en el archivo routes.py base para determinar en una URL entrante el
nombre de la aplicacin seleccionada. Cuando esto ocurre, se usa el routes.py especfico de
la aplicacin en lugar del routes.py base.
El formato de routes_app es idntico al de routes_in , excepto que el patrn de reemplazo es
simplemente el nombre de la aplicacin. Si al aplicar routes_app a la URL entrante no
devuelve un nombre de aplicacin, o el routes.py especfico de la aplicacin no se encuentra,
se utiliza el routes.py base.
Nota: routes_app `se agreg a partir de la versin 1.83 de web2py.

Aplicacin, controlador y funcin por defecto


Cuando se usa el sistema basado en patrones, el nombre de la aplicacin, controlador y
funcin por defecto se puede cambiar de init,default, e index respectivamente por otro
nombre configurando el valor apropiado en routes.py:
default_application = "miapp"
default_controller = "admin"
default_function = "comienzo"

Nota: Estos tems aparecieron por primera vez en la versin 1.83.

Enrutamiento y errores
Tambin puedes usar routes.py para reenrutar las solicitudes hacia acciones especiales en
caso de que ocurra un error en el servidor. Puedes especificar el mapeo (mapping) en forma
global, por aplicacin, por cdigo de error o por tipo de error para cada app. Un ejemplo:

routes_onerror = [
('init/400', '/init/default/login'),
('init/*', '/init/static/falla.html'),
('*/404', '/init/static/noseencuentra.html'),
('*/*', '/init/error/index')
]

Por cada tupla, la primera cadena es comparada con "[nombre de app]/[cdigo de error]". Si
hay una coincidencia, la solicitud fallida se reenruta hacia la URL de la segunda cadena de la
tupla que coincide. Si la URL de manejo de errores no es un archivo esttico, se pasarn a la
accin del error las siguientes variables GET:
o

code : el cdigo de status HTTP (por ejemplo, 404, 500)

ticket : de la forma "[nombre de app]/[nmero de ticket]" (o "None" si no hay un ticket)

requested_uri : el equivalente de request.env.request_uri

request_url : el equivalente de request.url

Estas variables sern accesibles para la accin de manejo de error por medio
de request.vars y se pueden usar para generar la respuesta con el error. En particular, es
buena idea que la accin del error devuelva el cdigo original de error HTTP en lugar del
cdigo de status 200 (OK) por defecto. Esto se puede hacer configurando response.status =
request.vars.code . Tambin es posible hacer que la accin del error enve (o encole) un

correo a un administrador, incluyendo un link al ticket en admin .


Los errores que no coinciden mostrarn una pgina de error por defecto. Esta pgina de error
por
defecto
tambin
se
puede
personalizar
aqu
(ver router.example.py y routes.example.py en la carpeta raz de web2py):
error_message = '<html><body><h1>%s</h1></body></html>'
error_message_ticket = '''<html><body><h1>Error interno</h1>
Ticket creado: <a href="/admin/default/ticket/%(ticket)s"
target="_blank">%(ticket)s</a></body></html>'''

La primera variable contiene el mensaje de error cuando se solicita una aplicacin o funcin
invlida. La segunda variable contiene el mensaje de error cuando se crea un ticket.

routes_onerror funciona con ambos mecanismos de enrutamiento

En "routes.py" puedes adems especificar una accin que se encargar de manejar los
errores:
error_handler = dict(application='error',
controller='default',
function='index')

Si se especifica el error_handler , la accin se llamar sin redirigir al usuario y se encargar


del manejo del error. Si la pgina para el manejo del error devolviera otro error, web2py
cambiar al comportamiento original devolviendo respuestas estticas.

Administracin de recursos estticos


A partir de la versin 2.1.0, web2py tiene la habilidad de administrar los recursos estticos.
Cuando una aplicacin est en etapa de desarrollo, los archivos estticos cambian a menudo,
por lo tanto web2py enva archivos estticos sin encabezados de cach. Esto tiene como
efecto secundario el "forzar" al navegador a que incluya los archivos estticos en cada
solicitud. Esto resulta en un bajo rendimiento cuando se carga la pgina.
En un sitio en "produccin", puedes necesitar servir archivos estticos con
encabezados cache para evitar las descargas innecesarias ya que los archivos estticos se
modifican.
Los encabezados cache permiten que el navegador recupere cada archivo por nica vez,
ahorrando de esta forma ancho de banda y reduciendo el tiempo de descarga.
De todos modos hay un problema: qu deberan declarar los encabezados cache? Cundo
deberan vencer el plazo para omitir la descarga de los archivos? Cuando se sirven los
archivos por primera vez, el servidor no puede pronosticar cundo se modificarn.
Una forma manual de resolverlo es creando subcarpetas para las distintas versiones de los
archivos estticos. Por ejemplo, se puede habilitar el acceso a una versin anterior de
"layout.css" en el URL "/miapp/static/css/1.2.3/layout.css". Cuando cambias el archivo, creas
una nueva subcarpeta y la enlazas como "/miapp/static/css/1.2.4/layout.css".
Este procedimiento funciona pero es molesto, porque cada vez que actualices un archivo css,
debers acordarte de copiarlo a otra carpeta, cambiar el URL del archivo en tu layout.html y
luego desplegar la aplicacin.

La administracin de recursos estticos resuelve el problema permitiendo al desarrollador


declarar la versin de un grupo de archivos estticos que se solicitarn nuevamente solo si ha
cambiado el nmero de versin. El nmero de versin se incluye en la ruta al archivo esttico
como en el ejemplo anterior. La diferencia con el mtodo anterior es que el nmero de versin
slo se muestra en el URL, pero se aplicar al sistema de archivos.
Si quieres servir "/myapp/static/layout.css" con los encabezados cache, solo debes incluir el
archivo con un URL distinto que incluya el nmero de versin:
/miapp/static/_1.2.3/layout.css

(Ten en cuenta que el URL define un nmero de versin, no se muestra en ningn otro lado).
Observa que el URL comienza con "/miapp/static/", seguido del nmero de versin compuesto
por un subguin y 3 enteros separados por puntos (como se describe en SemVer), y luego por
el nombre del archivo. Adems, ten en cuenta que no debes crear una carpeta "_1.2.3/".
Cada vez que el archivo esttico es solicitado indicando la versin en el url, se servir con un
encabezado cache especificando un valor de vencimiento muy lejano, especficamente.
Cache-Control : max-age=315360000
Expires: Thu, 31 Dec 2037 23:59:59 GMT

Esto significa que el navegador recuperar aquellos archivos por nica vez, y se guardarn por
un trmino indefinido (prcticamente sin vencimiento) en el cach del navegador. Si cambias el
nmero de versin en el URL, esto hace que el navegador piense que est solicitando un
archivo distinto, y el archivo se descarga nuevamente.
Puedes usar "_1.2.3", "_0.0.0", "_999.888.888", siempre y cuando la versin comience con un
subguin seguido de tres nmeros separados por puntos.
En desarrollo, puedes usar response.files.append(...) para enlazar los URL de los archivos
estticos. En este caso puedes incluir la parte "_1.2.3/" en forma manual, o puedes aprovechar
el nuevo parmetro del objeto response:
response.static_version .

Solo incluye los archivos estticos en la forma usual, por ejemplo


{{response.files.append(URL('static','layout.css'))}}

y en el modelo establece el valor

response.static_version = '1.2.3'

Esto
traducir
automticamente
cada
url
"/miapp/static/layout.css"
"/miapp/static/_1.2.3/layout.css" para cada archivo incluido en response.files .

en

A menudo en produccin optas por servir archivos estticos por medio del servidor web
(apache, nginx, etc.). Debes ajustar la configuracin de forma que se omita la parte que
contiene "_1.2.3/".
Por ejemplo, para Apache, cambia esto:
AliasMatch ^/([^/]+)/static/(.*)

/home/www-data/web2py/applications/$1/static/$2

por esto:
AliasMatch ^/([^/]+)/static/(?:/_[\d]+.[\d]+.[\d]+)?(.*)

/home/www-data/web2py/applications/

$1/static/$2

En forma similar, para Nginx, debes cambiar esto:


location ~* /(\w+)/static/ {
root /home/www-data/web2py/applications/;
expires max;
}

por esto:
location ~* /(\w+)/static(?:/_[\d]+.[\d]+.[\d]+)?/(.*)$ {
alias /home/www-data/web2py/applications/$1/static/$2;
expires max;
}

Ejecutando tareas en segundo plano


En web2py, cada solicitud http se sirve en un hilo (thread) propio. Los hilos se reciclan para
mayor eficiencia y son administrados por el servidor web. Por seguridad, el servidor establece
un tiempo lmite para cada solicitud. Esto significa que las acciones no deberan correr tareas
que toman demasiado tiempo, ni deberan crear nuevos hilos y tampoco deberan bifurcarse
(fork) en otros procesos (esto es posible pero no recomendable).

La forma adecuada para correr tareas prolongadas es hacerlo en segundo plano. No hay una
nica forma de hacerlo, pero aqu describiremos tres mecanismos que vienen incorporados en
web2py: cron, colas de tareas simples, y el planificador de tareas(scheduler).
Con respecto a cron, nos referimos a una funcionalidad de web2py y no al mecanismo Cron
de Unix. El cron de web2py funciona tambin en Windows.
El cron de web2py es el mtodo recomendado si necesitas tareas en segundo plano en
tiempos programados y estas tareas toman un tiempo relativamente corto comparado con el
tiempo transcurrido entre dos llamadas. Cada tarea corre en su proceso propio, y las distintas
tareas pueden ejecutarse simultneamente, pero no tienes control sobre la cantidad de tareas
que se ejecutan. Si por accidente una de las tareas se superpone con s misma, puede causar
el bloqueo de la base de datos y un pico en el uso de memoria.
El planificador de tareas de web2py tiene una metodologa distinta. La cantidad de procesos
corriendo es fija y estos pueden correr en distintos equipos. Cada proceso es llamado obrero
(worker). Cada obrero toma una tarea cuando est disponible y la ejecuta lo antes posible a
partir del tiempo programado, pero no necesariamente en el momento exacto para el que se
program. No puede haber ms procesos corriendo que el nmero de tareas programadas y
por lo tanto no habr picos del uso de memoria. Las tareas del planificador se pueden definir
en modelos y se almacenan en la base de datos. El planificador de web2py no implementa
una cola distribuida (distributed queue) porque se asume que el tiempo para la distribucin de
tareas es insignificante comparado con el tiempo para la ejecucin de las tareas. Los obreros
toman las tareas de la base de datos.
Las colas de tareas simples (homemade task queues) pueden ser una alternativa ms simple
al programador en algunos casos.

Cron
El cron de web2py provee a las aplicaciones de la habilidad para ejecutar tareas en tiempos
preestablecidos, de forma independiente con la plataforma.
Para cada aplicacin, la funcionalidad de cron se define en un archivo crontab:
app/cron/crontab

Este sigue la sintaxis definida en ref.


web2py).

[cron]

(con algunos agregados que son especficos de

Antes de web2py 2.1.1, cron se habilitaba por defecto y se poda deshabilitar con la
opcin de la lnea de comandos. A partir de 2.1.1 est deshabilitado por defecto y se
puede habilitar con la opcin -Y . Este cambio fue motivado por el deseo de promover el

uso del nuevo planificador (que tiene un mecanismo ms avanzado que cron) y tambin
porque el uso de cron puede incidir en el rendimiento.
Esto significa que cada aplicacin puede tener una configuracin de cron propia y separada y
que esta configuracin se puede cambiar desde web2py sin modificar el sistema operativo
anfitrin.
Aqu se muestra un ejemplo:
0-59/1 * * * * root python /path/to/python/script.py
30

3 * * * root *applications/admin/cron/limpieza_db.py

*/30

* * * * root **applications/admin/cron/algo.py

@reboot root

*mycontroller/mifuncion

@hourly root

*applications/admin/cron/expire_sessions.py

Las ltimas dos lneas en este ejemplo usan extensiones a la sintaxis normal de cron que dan
funcionalidad adicional de web2py.

El archivo "applications/admin/cron/expire_sessions.py" en realidad existe y viene con la


app admin. Busca sesiones vencidas y las elimina. "applications/admin/cron/crontab"
corre esta tarea cada hora.
Si la tarea/script tiene el prefijo asterisco ( * ) y termina en .py , se ejecuta en el entorno de
web2py. Esto quiere decir que tendrs todos los controladores y modelos a tu disposicin. Si
usas dos asteriscos ( ** ), los modelos no se ejecutarn. Este es el mtodo recomendado para
los llamados, ya que tiene una menor sobrecarga (overhead) y evita potenciales problemas de
bloqueo.
Ten en cuenta que los scripts o funciones ejecutadas en el entorno de web2py requieren
un db.commit() manual al final de la funcin o la transaccin se revertir.
web2py no genera ticket o trazas (traceback) significativas en modo consola (shell), que es el
modo en el cual corre cron, por lo que debes procurar que tu cdigo de web2py corra sin
errores antes de configurarlo como tarea de cron, ya que posiblemente no podrs ver esos
errores cuando se ejecuten en cron. Es ms, ten cuidado con el uso de modelos: mientras que
la ejecucin ocurre en procesos separados, los bloqueos de base de datos se deben tener en
cuenta para evitar que las pginas tengan que esperar a tareas de cron que podran bloquear
la base de datos. Utiliza la sintaxis ** si no necesitas acceso a la base de datos en tu tarea
de cron.

Adems puedes llamar a una funcin de controlador en cron, en cuyo caso no hay necesidad
de especificar una ruta. El controlador y la funcin sern los de la aplicacin de origen. Se
debe tener especial cuidado con los problemas listados arriba. Ejemplo:
*/30 * * * * root *micontrolador/mifuncion

Si especificas @reboot en el primer campo del archivo crontab, la tarea correspondiente se


ejecuta slo una vez, al inicio de web2py. Puedes usar esta funcionalidad si deseas hacer
cach previo, comprobaciones o configuracin inicial de datos para una aplicacin al inicio de
web2py. Ten en cuenta que las tareas de cron se ejecutan en paralelo con la aplicacin --- si la
aplicacin no est lista para servir solicitudes antes de que la tarea cron haya finalizado,
deberas implementar las comprobaciones adecuadas. Ejemplo:
@reboot * * * * root *mycontroller/mifuncion

Segn cmo ests corriendo web2py, hay cuatro modos de operacin para el web2py cron.
o
o

"soft cron": disponible en todos los modos de ejecucin


"hard cron": disponible si se usa el servidor web incorporado (directamente o a travs
de mod_proxy de Apache)

"external cron": disponible si se tiene acceso al servicio de cron propio del sistema

Sin cron

El modo por defecto es hard cron si utilizas el servidor incorporado; en el resto de los casos,
es soft cron por defecto. El soft cron es el mtodo por defecto si utilizas CGI, FASTCGI o
WSGI (pero ten en cuenta que el soft cron no se habilita por defecto en el
archivo wsgihandler.py provisto con web2py).
Tus tareas se ejecutarn al realizarse la primer llamada (carga de pgina) a web2py a partir de
tiempo especificado en crontab; pero slo luego del proceso de la pgina, por lo que el usuario
no observar una demora. Obviamente, hay cierta incertidumbre con respecto al momento
preciso en que se ejecutar la tarea, segn el trfico que reciba el servidor. Adems, la tarea
de cron podra interrumpirse si el servidor web tiene configurado un tiempo lmite para la
descarga de la pgina. Si estas limitaciones no son aceptables, puedes optar por external
cron (cron externo). El soft cron es razonable como ltimo recurso, pero si tu servidor permite
otros mtodos cron, deberan tener prioridad.
El hard cron es el mtodo por defecto si ests utilizando el servidor web incorporado
(directamente o a travs de Apache con mod_proxy). El hard cron se ejecuta en un hilo

paralelo, por lo que a diferencia del soft cron, no existen limitaciones con respecto a la
precisin en el tiempo o a la duracin de la ejecucin de la tarea.
El cron externo no es la opcin por defecto en ninguna situacin, pero requiere que tengas
acceso a los servicios cron del sistema. Se ejecuta en un proceso paralelo, por lo que ninguna
de las limitaciones de soft cron tienen lugar. Este es el modo recomendado de uso de cron
bajo WSGI o FASTCGI.
Ejemplo de lnea a agregar al crontab del sistema, (por lo general /etc/crontab):
0-59/1 * * * * web2py cd /var/www/web2py/ && python web2py.py -J -C -D 1 >> /tmp/cron.output
2>&1

Si usas el cron externo, asegrate de agregar o bien -J (o --cronjob , que es lo mismo) como
se indica ms arriba, para que web2py sepa que se ejecuta esa por medio de cron. Web2py
establece estos valores internamente si se usa soft o hard cron .

Colas de tareas simples


Si bien cron es til para correr tareas en intervalos regulares de tiempo, no es siempre la mejor
solucin para correr tareas en segundo plano. Para este caso web2py provee la posibilidad de
correr cualquier script de Python como si estuviera dentro de un controlador:
python web2py.py -S app -M -R applications/app/private/myscript.py -A a b c

donde -S app le dice a web2py que corra "miscript.py" como "app", -M le dice a web2py que
ejecute los modelos, y -A a b c le pasa los argumentos opcionales de lnea de
comandos sys.args=['a', 'b', 'c'] a "miscript.py".
Este tipo de proceso en segundo plano no debera ejecutarse con cron (a excepcin quizs de
cron y la opcin @reboot) porque necesitas asegurarte de que no se correr ms de una
instancia al mismo tiempo. Con cron es posible que un proceso comience en la iteracin 1 y
no se complete para la iteracin 2, por lo que cron vuelve a comenzar, y nuevamente, y otra
vez - atascando de este modo el servidor.
En el capitulo 8, damos un ejemplo de cmo usar el mtodo anterior para enviar email.

Planificador de tareas (Scheduler, experimental)


El planificador de tareas de web2py funciona en forma muy similar a la cola de tareas
descripta en la subseccin anterior con algunas particularidades:

Provee de un mecanismo estndar para crear y programar y monitorear tareas.

No hay un nico proceso en segundo plano sino un conjunto de procesos obreros.

El trabajo de un obrero se puede supervisar porque sus estados, as como tambin los
estados de cada tarea, se almacenan en la base de datos.
Funciona sin web2py pero los detalles no estn documentados aqu.

El planificador no usa cron, sin embargo se podra usar el @reboot de cron para iniciar los
nodos de los obreros.
Se pueden consultar instrucciones para desplegar el planificador con Linux o Windows en el
captulo de recetas de implementacin.
En el planificador, una tarea es simplemente una funcin definida en un modelo (o en un
mdulo e importada en un modelo). Por ejemplo:
def tarea_sumar(a,b):
return a+b

Las tareas siempre se llamarn en el mismo entorno configurado para los controladores y por
lo tanto ven todas las variables globales definidas en los modelos, incluyendo las conexiones a
bases de datos ( db ). Las tareas se diferencian de las acciones en controladores en que no
estn asociadas con una solicitud HTTP y por lo tanto no hay un objeto request.env .

Recuerda que debes ejecutar db.commit() al final de cada tarea si contiene comandos de
modificacin de la base de datos. web2py por defecto aplica los cambios a las bases de
datos al finalizar las acciones, pero las tareas del planificador no son acciones.
Para habilitar el planificador debes instanciar la clase Scheduler en un modelo. La forma
recomendable de habilitar el planificador para tu aplicacin es crear un archivo del modelo
llamado scheduler.py y definir tu funcin all. Luego definir las funciones, puedes usar el
siguiente cdigo en el modelo:
from gluon.scheduler import Scheduler
planificador = Scheduler(db)

Si tus tareas estn definidas en un mdulo (en lugar de usar un modelo) puedes necesitar
reiniciar los obreros.
La tarea se planifica con

planificador.queue_task(tarea_sumar, pvars=dict(a=1, b=2))

Parmetros
El primer argumento de la clase Scheduler debe ser la base de datos que usar el
planificador para comunicarse con los obreros. Puede ser la db de la app u otra db especial
para el planificador, quizs una base de datos compartida por mltiples aplicaciones. Si usas
SQLite es recomendable el uso de bases de datos distintas para los datos de la app y para el
registro de las tareas para que la app contine respondiendo normalmente. Una vez que se
han definido las tareas y creado la instancia de Scheduler , solo hace falta iniciar los obreros.
Puedes hacerlo de varias formas:
python web2py.py -K miapp

inicia un obrero para la app miapp . Si quieres iniciar mltiples obreros para la misma app,
puedes hacerlo con solo pasar myapp, myapp como argumentos. Adems puedes pasar el
argumento group_names (sobrescribiendo el definido en el tu modelo) con
python web2py.py -K miapp:grupo1:grupo2,miotraapp:grupo1

Si tienes un modelo llamado scheduler.py puedes iniciar o parar a los obreros desde la
ventana por defecto de web2py (la que usas para establecer la direccin ip y el puerto).
Otra mejora interesante: si usas el servidor web incorporado, puedes iniciarlo junto con el
planificador con una nica lnea de cdigo (se asume que no quieres que se muestre ventana
de inicio, de lo contrario puedes usar el men "Schedulers")
python web2py.py -a contrasea -K miapp -X

Puedes pasar los parmetros usuales (-i, -p, aqu -a evita que la ventana se muestre), usa una
app en el parmetro -K y agrega un -X. El planificador correr en conjunto con el servidor
web!
La lista completa de los argumentos que acepta el planificador es:
Scheduler(
db,
tasks=None,
migrate=True,
worker_name=None,

group_names=None,
heartbeat=HEARTBEAT,
max_empty_runs=0,
discard_results=False,
utc_time=False
)

Vamos a detallarlos en orden:


o

db es la instancia de base de datos DAL donde se crearn las tablas del planificador.

tasks es un diccionario que asocia nombres a funciones. si no usas este parmetro, la

funcin se recuperar del entorno de la aplicacin.


o

worker_name es por defecto None. Tan pronto como se inicie el obrero, se generar

un nombre de obrero de tipo anfitrin-uuid. Si quieres especificarlo, asegrate de que sea


nico.
o

group_names se establece por defecto como [main]. Todas las tareas tienen un

parmetro group_name , que es por defecto main. Los obreros solo pueden tomar tareas
de su propio grupo.

Nota importante: Esto es til si tienes distintas instancias de obreros (por ejemplo en
distintas mquinas) y quieres asignar tareas a un obrero especfico. Otra nota importante:
Es posible asignar ms grupos a un obrero, y ellos pueden tambin ser todos iguales,
como por ejemplo ['migrupo', 'migrupo'] . Las tareas se distribuirn teniendo en cuenta que
un obrero con grupos ['migrupo', 'migrupo'] es capaz de procesar el doble de tareas que
un obrero con grupos ['migrupo'] .
o

heartbeat se configura por defecto en 3 segundos. Este parmetro es el que controla

cun
frecuentemente
un
planificador
comprobar
su
estado
en
la
tabla scheduler_worker y ver si existe alguna tarea pendiente de procesamiento con el
valorASSIGNED (asignada) para l.
o

max_emtpty_runs es por defecto 0; eso significa que el obrero continuar procesando

tareas siempre que contengan el valorASSIGNED. Si configuras este parmetro como un


valor, digamos, 10, un obrero finalizar instantneamente si su valor es ACTIVEy no
existen tareas con el valor ASSIGNED para ese obrero en un plazo de 10 iteraciones.

Una iteracin se entiende como el proceso de un obrero de bsqueda de tareas que tiene
una frecuencia de 3 segundos (o el valor establecido para heartbeat ).
discard_results es por defecto False. Si se cambia a True, no se crearn registros

scheduler_run.

Nota importante: los registros scheduler_run se crearn como antes para las tareas con
los valores de estado FAILED, TIMEOUT y STOPPEDtasks's.
utc_time es por defecto False. Si necesitas coordinar obreros que funcionan con

distintos husos horarios, o no tienes problemas con la hora de verano o solar, utilizando
fechas y horas de distintos pases, etc., puedes configurarlo como True. El planificador
respetar la hora UTC y funcionar omitiendo la hora local. Hay un detalle: debes
programar las tareas con la hora de UTC (para los parmetros start_time, stop_time, y
as sucesivamente).
Ahora tenemos la infraestructura que necesitbamos: hemos definido las tareas, hemos
informado al planificador sobre ellas e iniciamos el obrero o los obreros. Lo que queda por
hacer es la planificacin de las tareas en s.

Tareas
Las tareas se pueden planificar en forma programtica o a travs de appadmin. De hecho, una
tarea se planifica simplemente agregando una entrada en la tabla "scheduler_task", a la que
puedes acceder a travs de appadmin:
http://127.0.0.1:8000/miapp/appadmin/insert/db/scheduler_task

El significado de los campos en esta tabla es obvio. Los campos "args" y "vars" son los valores
a pasarse a la tarea en formato JSON. En el caso de "tarea_sumar" previo, un ejemplo de
"args" y "vars" podra ser:
args = [3, 4]
vars = {}

o
args = []
vars = {'a':3, 'b':4}

La tabla scheduler_task es la tabla donde se organizan las tareas.

Todas las tareas siguen un ciclo vital

Por defecto, cuando envas una tarea al planificador, este tiene el estado QUEUED. Si
necesitas que este se ejecute ms tarde, usa el parmetro start_time (por defecto es now). Si
por alguna razn necesitas asegurarte de que la tarea no se ejecutar antes de cierto horario
(quizs una consulta a un webservice que cierra a la 1 de la maana, un correo que no se
deba enviar al terminar el horario laboral, etc ...) puedes hacerlo estableciendo el
parmetro stop_time (por defecto es None). Si tu tarea NO es tomada por otro obrero antes
de stop_time ,

se

establecer

como EXPIRED.

Las

tareas

que

no

tengan

un

valor stop_time configurado o tomadas antes que el parmetro stop_time se asignan a un


obrero estableciendo el valor ASSIGNED. Cuando un obrero toma una tarea, su estado se
establece como RUNNING.
Las tareas que se ejecuten pueden dar como resultado los siguientes valores:
o

TIMEOUT cuando

hayan

pasado

ms

de n segundos

especificados

con

el

parmetro timeout (por defecto 60 segundos).


o

FAILED cuando se detecta una excepcin.

COMPLETED cuando se completan en forma exitosa.

Los valores para start_time y stop_time deberan ser objetos datetime. Para programar la
ejecucin de "mitarea" en un plazo de 30 segundos a partir de la hora actual, por ejemplo,
tendras que hacer lo siguiente:
from datetime import timedelta as timed
planificador.queue_task('mitarea',

start_time=request.now + timed(seconds=30))

En forma complementaria, puedes controlar la cantidad de veces que una tarea se debe
repetir (por ejemplo, puedes necesitar calcular la suma de ciertos datos con una frecuencia
determinada). Para hacerlo, establece el parmetro repeats (por defecto es 1, es decir, una
sola vez, si se establece 0, se repite indefinidamente). Puedes especificar la cantidad de
segundos que deben pasar con el parmetro period (que por defecto es 60 segundos).

El perodo de tiempo no se calcula entre la finalizacin de la primer tanda y el comienzo


de la prxima, sino entre el tiempo de inicio de la primera tanda y el tiempo de inicio del
ciclo que le sigue).
Adems puedes establecer la cantidad de veces que una funcin puede generar una
excepcin (por ejemplo cuando se recuperan datos de un webservice lento) y volver a incluirse
en la cola en lugar de detenerse con el estado FAILED si usas el parmetro retry_failed (por
defecto es 0, usa -1 para no detenerse).

Resumiendo: dispones de
o

period y repeats para replanificar automticamente una funcin

timeout para asegurarte que la funcin no exceda una cierta cantidad de tiempo de

ejecucin
o

retry_failed para controlar cuantas veces puede fallar una tarea

start_time y stop_time para planificar una funcin en un horario restringido

queue_task

y task_status

El mtodo:
scheduler.queue_task(function, pargs=[], pvars={}, **kwargs)

te permite agregar a la cola tareas a ejecutar por obreros. Acepta los siguientes parmetros:
function (obligatorio): puede ser el nombre de la tarea o una referencia a la funcin en

o
s.

pargs : son los argumentos que se deben parar a la tarea, almacenados como una lista

de Python.
pvars : son los pares de argumentos nombre-valor que se usarn en la tarea,

almacenados como diccionario de Python.


kwargs : otras columnas de scheduler_task que se pueden pasar como argumentos de

par nombre-valor (por ejemplo repeats, period, timeout).


Por ejemplo:
scheduler.queue_task('demo1', [1, 2])

hace exactamente lo mismo que


scheduler.queue_task('demo1', pvars={'a':1, 'b':2})

y lo mismo que
st.validate_and_insert(function_name='demo1', args=json.dumps([1, 2]))

y que:
st.validate_and_insert(function_name='demo1', vars=json.dumps({'a':1, 'b':2}))

He aqu un ejemplo ms complejo y completo:


def tarea_sumar(a, b):
return a + b

planificador = Scheduler(db, tasks=dict(demo1=tarea_sumar))

scheduler.queue_task('demo1', pvars=dict(a=1, b=2),


repeats = 0, period = 180)

Desde la versin 2.4.1, si pasas el argumento adicional inmediate=True har que el obrero
principal reorganice las tareas. Antes de 2.4.1, el obrero verificaba las nuevas tareas cada 5
ciclos (o sea, 5*heartbeat segundos). Si tenas una app que necesitaba comprobar
frecuentemente nuevas tareas, para lograr un comportamiento gil estabas obligado a
disminuir el parmetro heartbeat , exigiendo a la base de datos injustificadamente.
Con inmediate=True puedes forzar la comprobacin de nuevas tareas: esto ocurrir cuando
hayan transcurrido n segundos, con n equivalente al valor establecido para heartbeat .
Una llamada a planificador.queue_task devuelve el id y el uudi de la tarea que has agregado
a la cola (puede ser un valor que le hayas asignado o uno generado automticamente), y los
erroes posibles errors :
<Row {'errors': {}, 'id': 1, 'uuid': '08e6433a-cf07-4cea-a4cb-01f16ae5f414'}>

Si existen errores (usualmente errores sintcticos o de validacin de los argumentos de


entrada), obtienes el resultado de la validacin, e id y uuid sern None
<Row {'errors': {'period': 'ingresa un entero mayor o igual a 0'}, 'id': None, 'uuid': None}>

Salida y resultados
La tabla "scheduler_run" almacena los estados de toda tarea en ejecucin. Cada registro hace
referencia a una tarea que ha sido tomada por un obrero. Una tarea puede ejecutarse ms de
una vez. Por ejemplo, una tarea programada para repetirse 10 veces por hora probablemente
se ejecute 10 veces (a menos que una falle o que tomen en total ms de una hora). Ten en
cuenta que si la tarea no devuelve valores, se elimina de la tabla scheduler_run una vez que
finalice.
Los posibles estados son
RUNNING, COMPLETED, FAILED, TIMEOUT

Si se completa la ejecucin, no se generaron excepciones y no venci la tarea, la ejecucin se


marca como COMPLETED y la tarea se marca como QUEUED o COMPLETED segn si se
supone que se debe ejecutar nuevamente o no. La salida de la tarea se serializa como JSON
y se almacena en el registro de ejecucin.

Cuando una tarea con estado RUNNING genera un excepcin, tanto la ejecucin como la
tarea se marcan con FAILED . La traza del error se almacena en el registro.
En una forma similar, cuando una ejecucin supera el plazo de vencimiento, se detiene y tanto
la ejecucin como la tarea se marcan con TIMEOUT .
En todo caso, se captura el stdout y adems se almacena en el registro de la ejecucin.
Usando appadmin, uno puede comprobar todas las tareas en ejecucin RUNNING , la salida
de las tareas finalizadas COMPLETE , el error en las tareas FAILED , etc.
El planificador tambin crea una tabla ms llamada "scheduler_worker", que almacena el
heartbeat de los obreros y sus estados.

Administracin de procesos
El manejo pormenorizado de los obreros es difcil. Este mdulo intenta una implementacin
comn para todas las plataformas (Mac, Win, Linux).
Cuando inicias un obrero, puedes necesitar en algn momento:
o

matarlo "sin importar lo que est haciendo"

matarlo solo si no est procesando tareas

desactivarlo

Quizs tengas todava algunas tareas en la cola, y quieres ahorrar recursos. Sabes que las
quieres procesar a cada hora, por lo que necesitars:
o

procesar todas las tareas y finalizar automticamente

Todas estas cosas son posibles administrando los parmetros de Scheduler o la


tabla scheduler_worker . Para ser ms precisos, para los obreros que han iniciado puedes
cambiar el valor de estado de cualquiera para modificar su comportamiento. Igual que con las
tareas, los obreros pueden tener uno de los siguientes estados: ACTIVE, DISABLED,
TERMINATE or KILLED.
ACTIVE y DISABLED son "permanentes", mientras que TERMINATE o KILL, como sugieren
los nombres de estado, son ms bien "comandos" antes que estados.
El uso de la combinacin de teclas ctrl+c equivale a establecer un estado de obrero
como KILL

Hay algunas funciones convenientes a partir de la versin 2.4.1 (que no necesitan mayor
descripcin).
scheduler.disable() # deshabilitar
scheduler.resume() # continuar
scheduler.terminate() # finalizar
scheduler.kill() # matar

cada funcin toma un parmetro opcional, que puede ser una cadena o una lista, para
administrar obreros segn sus grupos group_names . Por defecto es equivalente a los valores
de group_names , definidos crear la instancia del planificador.
Un ejemplo es mejor que cien palabras: scheduler.terminate('alta_prioridad') CANCELAR
todos

los

obreros

que

estn

que sheduler.terminate(['alta_prioridad',

procesando

tareas alta_prioridad ,

'baja_prioridad']) cancelar

mientras

todos

los

obreros alta_prioridad y baja_prioridad .

Cuidado: si tienes un obrero


procesando alta_prioridad y baja_prioridad , scheduler.terminate('alta_prioridad') cancelar
el obrero para todo el conjunto, incluso si no quieres cancelar las tareas baja_prioridad .

Todo lo que se puede hacer a travs de appadmin tambin puede hacerse insertando o
modificando los registros de esas tablas.
De todas formas, uno no debera modificar registros relacionados con tareas en
ejecucin RUNNING ya que esto puede generar un comportamiento inesperado. Es mejor
prctica agregar tareas a la cola usando el mtodo "queue_task".
Por ejemplo:
scheduler.queue_task(
function_name='tarea_sumar',
pargs=[],
pvars={'a':3,'b':4},
repeats = 10, # correr 10 veces
period = 3600, # cada 1 hora
timeout = 120, # debera tomar menos de 120 segundos
)

Observa que los campos "times_run", "last_run_time" y "assgned_worker_name" no se


especifican al programarse sino son completados automticamente por los trabajadores.
Tambin puedes recuperar la salida de las tareas completadas:
ejecuciones_finalizadas = db(db.scheduler_run.run_status='COMPLETED').select()

El planificador se considera en fase experimental porque puede necesitar pruebas ms


intensivas y porque la estructura de tablas puede cambiar en caso de agregarse ms
caractersticas.

Informando porcentajes
Hay una palabra especial para los comandos print en tus funciones que limpia toda la salida
anterior. Esa palabra es !clear! . Esto, combinado con el parmetro sync_output , permite
generar informes de porcentajes.
He aqu un ejemplo:
def informe_de_porcentajes():
time.sleep(5)

print '50%'
time.sleep(5)
print '!clear!100%'
return 1

La funcin informe_de_porcentajes est inactiva durante 5 segundos, luego devuelve 50% .


Entonces, cesa la actividad por otros 5 segundos y por ltimo devuelve 100% . Ten en cuenta
que la salida en la tabla sheduler_run se sincroniza cada dos segundos y que el segundo
comando print que contiene !clear!100% hace que se limpie el 50% y se reemplace
por 100% .
scheduler.queue_task(informe_de_porcentajes,
sync_output=2)

Mdulos de terceros
web2py est desarrollado en Python, por lo que puede importar y utilizar cualquier mdulo de
Python, incluyendo los mdulos de terceros. Slo necesita poder hallarlos. Como con
cualquier aplicacin de Python, los mdulos se pueden instalar en la carpeta oficial de Python
"site-packages", y se pueden importar desde cualquier ubicacin en tu cdigo.
Los mdulos en la carpeta "site-packages" son, como lo sugiere el nombre, paquetes del
entorno/sistema. Las aplicaciones que requieren estos paquetes no son porttiles a menos
que esos mdulos se instalen por separado. La ventaja del uso de mdulos en "site-packages"
es que las distintas aplicaciones los pueden compartir. Consideremos, por ejemplo, un
paquete para ploteo llamado "matplotlib". Puedes instalarlo desde la consola usando el
comando easy_install de PEAK[easy-install] (o la alternativa ms moderna pip [PIP]):
easy_install py-matplotlib

y luego puedes importarlo en un modelo/controlador/vista con:


import matplotlib

La distribucin de cdigo fuente de web2py y la distribucin binaria de Windows tiene un sitepackages en la carpeta raz. La distribucin binaria para Mac tiene una carpeta site-packages
en la ruta:
web2py.app/Contents/Resources/site-packages

El problema al usar site-packages es que se torna difcil el uso de distintas versiones de un


mismo mdulo al mismo tiempo, por ejemplo podra haber dos aplicaciones que usen distintas
versiones del mismo archivo. En este ejemplo, sys.path no se puede alterar porque afectara
a ambas aplicaciones.
Para estas situaciones, web2py provee de otra forma de importar mdulos de forma que
el sys.path global no se altere: ubicndolos en la carpeta "modules" de una aplicacin
determinada. Una ventaja de esta tcnica es que el mdulo se copiar y distribuir
automticamente con la aplicacin.

Una vez que un mdulo "mimodulo.py" se ubica en la carpeta "modules/" de una app, se
puede importar desde cualquier ubicacin dentro de una aplicacin de web2py (sin
necesidad de modificar sys.path ) con:
import mimodulo

Entorno de ejecucin
Si bien todo lo descripto aqu es vlido, es recomendable armar la aplicacin usando
componentes, como se detalla en el captulo 12.
Los archivos de modelo y controlador no son mdulos de Python en el sentido de que no se
pueden importar usando la instruccin import . La razn es que los modelos y controladores
estn diseados para ejecutarse en un entorno preparado que se ha preconfigurado con los
objetos globales de web2py (request, response, session, cache y T) y funciones ayudantes.
Esto es necesario porque Python es un lenguaje de espacios estticos (statically -lexicallyscoped language), mientras que el entorno de web2py se crea en forma dinmica.
web2py provee de una funcin exec_environment que te permite acceder a los modelos y
controladores directamente. exec_evironment crea un entorno de ejecucin de web2py, carga
el archivo en l y devuelve un objeto Storage que contiene el entorno. El objeto Storage
adems sirve como mecanismo de espacio de nombres. Todo archivo de Python diseado
para que corra en el entorno de ejecucin se puede cargar con exec_environment . Los usos
de exec_environment incluyen:
o

Acceso a datos (modelos) desde otras aplicaciones.

Acceso a objetos globales desde otros modelos o controladores.

Ejecucin de funciones de otros controladores.

Carga de libreras de ayudantes para todo el sitio/sistema.

El siguiente ejemplo lee registros de la tabla user en la aplicacin cas :


from gluon.shell import exec_environment
cas = exec_environment('applications/cas/models/db.py')
registros = cas.db().select(cas.db.user.ALL)

Otro ejemplo: supongamos que tenemos un controlador "otro.py" que contiene:


def una_accion():
return dict(direccion_remota=request.env.remote_addr)

Esto se puede llamar desde otra accin de la siguiente forma (o desde la consola de web2py):
from gluon.shell import exec_environment
otro = exec_environment('applications/app/controllers/otro.py', request=request)
resultado = otro.una_accion()

En la lnea 2, request=request es opcional. Tiene el efecto de pasar la solicitud actual al


entorno de "otro". Sin ese argumento, el entorno contendra un objeto request nuevo y vaco
(excepto por request.folder ). Tambin es posible pasar un objeto response y session
a exec_environment . Ten cuidado al pasar los objetos request, response y session --- las
modificaciones en la accin llamada o sus dependencias pueden dar lugar a efectos no
esperados.
La llamada a la funcin en la lnea 3 no ejecuta la vista; slo devuelve el diccionario a menos
que response.render se llame explcitamente por "una_accion".
Un detalle ms a observar: no utilices exec_environment en forma inapropiada. Si quieres que
los resultados de las acciones se recuperen en otra aplicacin, probablemente deberas
implementar una API XML-RPC (la implementacin de una API XML-RPC con web2py es
prcticamente trivial). No utilices exec_environment como mecanismo de redireccin; utiliza el
ayudante redirect .

Cooperacin
Hay varias formas de cooperacin entre aplicaciones:
o

Las aplicaciones pueden conectarse a la misma base de datos y por lo tanto,


compartir las tablas. No es necesario que todas las tablas en la base de datos se definan

en cada aplicacin, pero se deben definir en las aplicaciones que las usan. Todas las
aplicaciones que usan la misma tabla excepto una de las aplicaciones, deben definir la
tabla con migrate=False .
o

Las aplicaciones pueden embeber componentes desde otras aplicaciones usando el


ayudante LOAD (descripto en el captulo 12).
Las aplicaciones pueden compartir sesiones.

Las aplicaciones pueden llamar a las acciones de otras aplicaciones en forma remota
a travs de XML-RPC.

Las aplicaciones pueden acceder a los archivos de otras aplicaciones a travs del
sistema de archivos (se asume que las aplicaciones comparten el sistema de archivos).

Las aplicaciones pueden llamar a las acciones de otras aplicaciones en forma local
utilizando exec_environment como se detalla ms arriba.

Las aplicaciones pueden importar mdulos de otras aplicaciones usando la sintaxis:

from applications.nombreapp.modules import mimodulo

Las aplicaciones pueden importar cualquier mdulo en las rutas de bsqueda


del PYTHONPATH y sys.path .
Una app puede cargar la sesin de otra app usando el comando:

session.connect(request, response, masterapp='nombreapp', db=db)

Aqu "nombreapp" es el nombre de la aplicacin maestra, es decir, la que establece la


sesin_id inicial en la cookie. db es una conexin a la base de datos que contiene la tabla de
la sesin ( web2py_session ). Todas las app que comparten sesiones deben usar las misma
base de datos para almacenar las sesiones.
o

Una aplicacin puede cargar un mdulo desde otra app usando

import applications.otraapp.modules.otromodulo

Historial o logging
Python provee de distintas API para historial o logging. web2py dispone de un mecanismo
para configurarlo para que las app lo puedan usar.
En tu aplicacin, creas un logger, por ejemplo en un modelo:

import logging
logger = logging.getLogger("web2py.app.miapp")
logger.setLevel(logging.DEBUG)

y puedes usarlo para registrar (log) mensajes de distinta importancia


logger.debug("Slo comprobando que %s" % detalles)
logger.info("Deberas saber que %s" % detalles)
logger.warn("Cuidado que %s" % detalles)
logger.error("Epa, algo malo ha ocurrido %s" % detalles)
logging es un mdulo estndar de Python que se detalla aqu:
http://docs.python.org/library/logging.html

La cadena "web2py.app.miapp" define un logger en el nivel de la aplicacin.


Para que esto funcione adecuadamente, necesitas un archivo de configuracin para el logger.
Hay un archivo incluido en la instalacin de web2py en la carpeta raz, "logging.example.conf".
Debes cambiar el nombre del archivo como "logging.conf" y personalizarlo segn tus
requerimientos.
Este archivo contiene documentacin de uso, por lo que es conveniente que lo abras y lo leas.
Para crear un logger configurable para la aplicacin "miapp", debes agregar miapp a la lista de
claves [loggers]:
[loggers]
keys=root,rocket,markdown,web2py,rewrite,app,welcome,miapp

y debes agregar una seccin [logger_miapp], usando [logger_welcome] como ejemplo.


[logger_myapp]
level=WARNING
qualname=web2py.app.miapp
handlers=consoleHandler
propagate=0

La directiva "handlers" especifica el tipo de historial y, para el ejemplo, la salida del historial
para miapp se muestra por consola.

WSGI
web2py y WSGI tienen una relacin de amor-odio. Nuestra opinin es que WSGI fue
desarrollado como protocolo para conectar servidores web a aplicaciones web en forma
porttil, y lo usamos con ese fin. web2py en su ncleo es una aplicacin
WSGI: gluon.main.wsgibase . Algunos desarrolladores han llevado a WSGI a sus lmites como
protocolo para comunicaciones middleware y desarrollan aplicaciones web en forma de
cebolla, con sus mltiples capas (cada capa es un middleware desarrollado en forma
independiente de la totalidad del marco de desarrollo). web2py no adopta esta estructura en
forma interna. Esto se debe a que creemos que las funcionalidades del ncleo de los marcos
de desarrollo (manejo de las cookie, sesin, errores, transacciones, manejo de las URL
o dispatching) se pueden optimizar para que sean ms seguras y veloces si son manejadas
por una nica capa que las incluya.
De todos modos, web2py te permite el uso de aplicaciones WSGI de terceros y middleware en
tres formas (y sus combinaciones):
o

Puedes editar el archivo "wsgihandler.py" e incluir cualquier middleware WSGI de


terceros.

Puedes conectar middleware WSGI de terceros a cualquier accin especfica en tus


app.

Puedes llamar a una app WSGI de terceros desde tus acciones.

La nica limitacin es que no puedes usar middleware de terceros para reemplazar las
funciones del ncleo de web2py.

Middleware externo
Consideremos el archivo "wsgibase.py":
#...
LOGGING = False
#...
if LOGGING:
aplicacion = gluon.main.appfactory(wsgiapp=gluon.main.wsgibase,

logfilename='httpserver.log',
profilerfilename=None)
else:
aplicacion = gluon.main.wsgibase

Cuando LOGGING se establece como True , gluon.main.wsgibase es envuelto (wrapped) por


la funcin middleware gluon.main.appfactory . Esta provee de registro del historial en el archivo
"httpserver.log". En forma similar puedes agregar cualquier middleware de terceros. Se puede
encontrar ms informacin sobre este tema en la documentacin oficial de WSGI.

Middleware interno
Dada cualquier accin en tus controladores (por ejemplo index ) y cualquier aplicacin
middleware de terceros (por ejemplo MiMiddleware , que convierte la salida a maysculas),
puedes usar un decorador de web2py para aplicar el middleware a esa accin. Este es un
ejemplo:
class MiMiddleware:
"""Convertir la salida a maysculas"""
def __init__(self, app):
self.app = app
def __call__(self, entorno, iniciar_respuesta):
elementos = self.app(entorno, iniciar_respuesta)
return [item.upper() for item in elementos]

@request.wsgi.middleware(MyMiddleware)
def index():
return 'hola mundo'

No podemos garantizar que todo middleware de terceros funcione con este mecanismo.

Llamando a aplicaciones WSGI


Es fcil llamar a una app WSGI desde una accin en web2py. Este es un ejemplo:

def probar_app_wsgi(entorno, iniciar_respuesta):


"""Esta es una app WSGI para prueba"""
estado = '200 OK'
encabezados_respuesta = [('Content-type','text/plain'),
('Content-Length','13')]
iniciar_respuesta(estado, encabezados_respuesta)
return ['hola mundo!\n']

def index():
"""Una accin para prueba que llama a la app previa y escapa la salida"""
elementos = probar_app_wsgi(request.wsgi.environ,
request.wsgi.start_response)
for item in elementos:
response.write(item, escape=False)
return response.body.getvalue()

En este caso, la accin index llama a probar_app_wsgi y escapa el valor obtenido antes de
devolverlo. Observa que index por s misma no es una app WSGI y debe usar la API normal
de web2py (por ejemplo response.write para escribir en el socket).

Las vistas
web2py usa Python para sus modelos, controladores y vistas, aunque lo hace con una sintaxis
levemente modificada en las vistas para permitir un cdigo ms legible, pero sin imponer
restricciones al uso correcto del lenguaje.
El propsito de una vista es el de embeber cdigo (Python) en un documento HTML. En
general, esto trae algunos problemas:
o

Cmo debera realizarse el escapado del cdigo embebido?

El espaciado debera responder a las normas de Python o a las de HTML?

web2py utiliza {{ ... }} para escapado de cdigo Python embebido en HTML. La ventaja del
uso de llaves en vez de corchetes es que los primeros son transparentes para los editores ms

utilizados. Esto permite al desarrollador el uso de esos editores para crear vistas de web2py.
Los delimitadores se pueden cambiar por ejemplo con
response.delimiters = ('<?','?>')

Si se agrega esa lnea en el modelo se aplicar en toda la aplicacin, si se agrega a un


controlador slo a las vistas asociadas a acciones de ese controlador, si se incluye en una
accin slo en la vista para esa accin.
Como el desarrollador est embebiendo cdigo Python en el HTML, se debera aplicar las
reglas de espaciado de HTLM, no las reglas de Pyhton. Por lo tanto, permitimos cdigo Python
sin espaciado dentro de las etiquetas {{ ... }} . Como Python normalmente usa espaciado
para delimitar bloques de cdigo, necesitamos una forma diferente para delimitarlos; es por
eso que el lenguaje de plantillas hace uso de la palabra pass .

Un bloque de cdigo comienza con una lnea terminada con punto y coma y finaliza con
una lnea que comienza con pass . La palabra pass no es necesaria cuando el final del
bloque es obvio para su contexto.
Aqu hay un ejemplo:
{{
if i == 0:
response.write('i es 0')
else:
response.write('i no es 0')
pass
}}

Ten en cuenta que pass es una palabra de Python, no de web2py. Algunos editores de
Python, como Emacs, usan la palabra pass para sealar la divisin de bloques y la emplean
en la modificacin automtica del espaciado.
El lenguaje de plantillas de web2py hace exactamente lo mismo. Cuando encuentra algo
como:
<html><body>
{{for x in range(10):}}{{=x}}hola<br />{{pass}}
</body></html>

lo traduce en un programa:
response.write("""<html><body>""", escape=False)
for x in range(10):
response.write(x)
response.write("""hola<br />""", escape=False)
response.write("""</body></html>""", escape=False)
response.write escribe en response.body .

Cuando hay un error en una vista de web2py, el reporte del error muestra el cdigo generado
de la vista, no la vista real escrita por el desarrollador. Esto ayuda al desarrollador en la
depuracin del cdigo resaltando la seccin que se ejecuta (que se puede depurar con un
editor de HTML o el inspector del DOM del navegador).
Adems, ten en cuenta que:
{{=x}}

genera
response.write(x)

Las variables inyectadas en el HTML de esta forma se escapan por defecto. El escapado es
ignorado si x es un objeto XML , incluso si se ha establecido escape como True .
Aqu hay un ejemplo que introduce el ayudante H1 :
{{=H1(i)}}

que se traduce como:


response.write(H1(i))

al evaluarse, el objeto H1 y sus componentes se serializan en forma recursiva, se escapan y


escriben en el cuerpo de la respuesta. Las etiquetas generadas por H1 y el HTML incluido no
se escapan. Este mecanismo garantiza que el texto -- y slo el texto -- mostrado en la pgina
web se escapa siempre, previniendo de esa forma vulnerabilidades XSS. Al mismo tiempo, el
cdigo es simple y fcil de depurar.

El mtodo response.write(obj, escape=True) toma dos argumentos, el objeto a escribirse y si


se debe escapar (con valor True por defecto). Si obj tiene un mtodo .xml() , se llama y el
resultado se escribe en el cuerpo de la respuesta (el argumento escape se ignora). De lo
contrario, usa el mtodo __str__ del objeto para serializarlo y, si el argumento escape
es True , lo escapa. Todos los ayudantes incorporados ( H1 por ejemplo) son objetos que
saben cmo serializarse a s mismos a travs del mtodo .xml() .
Todo esto se hace en forma transparente. Nunca necesitas (y nunca deberas) llamar al
mtodo response.write en forma explcita.

Sintaxis bsica
El lenguaje de plantillas de web2py soporta todas las estructuras de control de Python. Aqu
damos algunos ejemplos de cada uno. Se pueden anidar segn las convenciones usuales de
programacin.
for...in

En plantillas puedes realizar bucles de objetos iterable:


{{items = ['a', 'b', 'c']}}
<ul>
{{for item in items:}}<li>{{=item}}</li>{{pass}}
</ul>

que produce:
<ul>
<li>a</li>
<li>b</li>
<li>c</li>
</ul>

Aqu items es todo iterable como por ejemplo un list o tuple de Python o un objeto Rows, o
cualquier objeto que implemente un iterator. Los elementos mostrados son previamente
serializados y escapados.

while

Para crear un bucle puedes usar la palabra while:


{{k = 3}}
<ul>
{{while k > 0:}}<li>{{=k}}{{k = k - 1}}</li>{{pass}}
</ul>

que produce:
<ul>
<li>3</li>
<li>2</li>
<li>1</li>
</ul>

if...elif...else

Puedes usar estructuras condicionales:


{{
import random
k = random.randint(0, 100)
}}
<h2>
{{=k}}
{{if k % 2:}}es impar{{else:}}es par{{pass}}
</h2>

que genera:
<h2>
45 es impar
</h2>

Como es obvio que else termina el primer bloque if , no hay necesidad de una
instruccin pass , y el uso de esa instruccin sera incorrecta. En cambio, debes cerrar
explcitamente el bloque else con un pass .
Recuerda que en Python "else if" se escribe elif como en el siguiente ejemplo:
{{
import random
k = random.randint(0, 100)
}}
<h2>
{{=k}}
{{if k % 4 == 0:}}es divisible por 4
{{elif k % 2 == 0:}}es par
{{else:}}es impar
{{pass}}
</h2>

Eso genera:
<h2>
64 es divisible por 4
</h2>

try...except...else...finally

Es posible usar estructuras try...except en vistas con un detalle. Tomemos el siguiente


ejemplo:
{{try:}}
Hola {{= 1 / 0}}
{{except:}}
divisin por cero
{{else:}}
no hay divisin por cero

{{finally}}
<br />
{{pass}}

Esto generar la siguiente salida:


Hola
divisin por cero
<br />

Este ejemplo muestra que toda la salida generada antes de que la excepcin ocurra se
convierte (incluyendo la salida que precede a la excepcin) dentro del bloque try. "Hola" se
escribe porque precede a la excepcin.
def...return

El lenguaje de plantillas de web2py permite al desarrollador que defina e implemente


funciones que pueden devolver cualquier objeto Python o una cadena con HTML. Aqu
tenemos en cuenta dos ejemplos:
{{def itemize1(link): return LI(A(link, _href="http://" + link))}}
<ul>
{{=itemize1('www.google.com')}}
</ul>

produce la siguiente salida:


<ul>
<li><a href="http:/www.google.com">www.google.com</a></li>
</ul>

La funcin itemize1 devuelve un ayudante que se inserta en la ubicacin donde la funcin se


llama.
Observa ahora el siguiente ejemplo:
{{def itemize2(link):}}
<li><a href="http://{{=link}}">{{=link}}</a></li>

{{return}}
<ul>
{{itemize2('www.google.com')}}
</ul>

Este produce exactamente la misma salida que arriba. En este caso la


funcin itemize2 representa una seccin de HTML que va a reemplazar la etiqueta de
web2py donde se ha llamado a la funcin. Ten en cuenta que no hay '=' delante de la llamada
a itemize2 , ya que la funcin no devuelve el texto, sino que lo escribe directamente en la
respuesta.
Hay un detalle: las funciones definidas dentro de una vista deben terminar con una instruccin
return, o el espaciado automtico fallar.

Ayudantes HTML
Si tenemos el siguiente cdigo en una vista:
{{=DIV('esta', 'es', 'una', 'prueba', _id='123', _class='miclase')}}

este se procesa como:


<div id="123" class="miclase">estaesunaprueba</div>
DIV es una clase ayudante, es decir, algo que se puede usar para armar HTML en forma

programtica. Se corresponde con la etiqueta <div> de HTML.


Los argumentos posicionales se interpretan como objetos contenidos entre las etiquetas de
apertura y cierre. Los pares nombre-valor o (named argument) que comienzan con subguin
son interpretados como atributos HTML (sin subguin). Algunos ayudantes tambin tienen
pares nombre-valor que no comienzan con subguin; estos valores son especficos de la
etiqueta HTML.
En lugar de un conjunto de argumentos sin nombre, un ayudante puede tambin tomar una
lista o tupla como conjunto de componentes usando la notacin * y puede tomar un
diccionario como conjunto de atributos usando ** , por ejemplo:
{{
contenido = ['este','es','un','prueba']

atributos = {'_id':'123', '_class':'miclase'}


=DIV(*contenido,**atributos)
}}

(genera la misma salida que antes).


La siguiente lista de ayudantes:
A , B , BEAUTIFY , BODY , BR , CAT , CENTER , CODE , COL , COLGROUP , DIV , EM , EMBE
D , FIELDSET , FORM , H1 , H2 , H3 , H4 , H5 , H6 , HEAD , HR , HTML , I , IFRAME , IMG ,
INPUT , LABEL , LEGEND , LI , LINK , MARKMIN , MENU , META , OBJECT , ON , OL , OPTGR
OUP , OPTION , P , PRE , SCRIPT , SELECT , SPAN , STYLE , TABLE , TAG , TBODY , TD , TEXT
AREA , TFOOT , TH , THEAD , TITLE , TR , TT , UL , URL , XHTML , XML , embed64 , xmlesc
ape

se puede usar para construir expresiones complejas que se pueden serializar como XML [xml-w] [xmlo]
. Por ejemplo:
{{=DIV(B(I("hola ", "<mundo>"))), _class="miclase")}}

produce:
<div class="miclase"><b><i>hola &lt;mundo&gt;</i></b></div>

Los ayudantes tambin pueden serializarse como cadenas, indistintamente, con los
mtodos __str__ y xml :
>>> print str(DIV("hola mundo"))
<div>hola mundo</div>
>>> print DIV("hola mundo").xml()
<div>hola mundo</div>

El mecanismo de ayudantes en web2py es ms que un sistema para generar HTML sin


mtodos de concatenacin. Este provee una representacin del lado del servidor del
Document Object Model (DOM).
Los componentes de los ayudantes se pueden recuperar por su posicin, y los ayudantes
funcionan como listas con respecto a sus componentes:
>>> a = DIV(SPAN('a', 'b'), 'c')

>>> print a
<div><span>ab</span>c</div>
>>> del a[1]
>>> a.append(B('x'))
>>> a[0][0] = 'y'
>>> print a
<div><span>yb</span><b>x</b></div>

Los atributos de los ayudantes se pueden recuperar por nombre, y los ayudantes funcionan
como diccionarios con respecto a sus atributos:
>>> a = DIV(SPAN('a', 'b'), 'c')
>>> a['_class'] = 's'
>>> a[0]['_class'] = 't'
>>> print a
<div class="s"><span class="t">ab</span>c</div>

Ten en cuenta que el conjunto completo de componentes puede recuperarse a travs de una
lista llamada a.components , y el conjunto completo de atributos se puede recuperar a travs
de

un

diccionario

llamado a.attributes .

a a.components[i] cuando i es

un

entero,

Por

lo
y a[s] es

que, a[i] equivale


equivalente

a a.attributes[s] cuando s es una cadena.


Observa que los atributos de ayudantes se pasan como pares de argumentos nombre-valor
(named argument) al ayudante. En algunos casos, sin embargo, los nombres de atributos
incluyen caracteres especiales que no son elementos vlidos en variables de Python (por
ejemplo, los guiones) y por lo tanto no se pueden usar en pares de argumentos nombre-valor.
Por ejemplo:
DIV('text', _data-role='collapsible')

No funcionar porque "_data-role" incluye un guin, que producira un error sintctico de


Python.
En esos casos, sin embargo, puedes pasar los atributos como un diccionario y utilizar la
notacin ** para argumentos de funcin, que asocia un diccionario de pares (nombre:valor) a
un conjunto de pares de argumentos nombre-valor.

>>> print DIV('text', **{'_data-role': 'collapsible'})


<div data-role="collapsible">text</div>

Tambin puedes crear etiquetas (TAG) especiales en forma dinmica:


>>> print TAG['soap:Body']('algo',**{'_xmlns:m':'http://www.example.org'})
<soap:Body xmlns:m="http://www.example.org">algo</soap:Body>

XML
XML es un objeto que se utiliza para encapsular texto que no debera escaparse. El texto

puede o no contener XML vlido. Por ejemplo, puede contener JavaScript.


El texto de este ejemplo se escapa:
>>> print DIV("<b>hola</b>")
&lt;b&gt;hola&lt;/b&gt;

con el uso de XML puedes prevenir el escapado:


>>> print DIV(XML("<b>hola</b>"))
<b>hola</b>

A veces puedes necesitar convertir HTML almacenado en una variable, pero el HTML puede
contener etiquetas inseguras como scripts:
>>> print XML('<script>alert("no es seguro!")</script>')
<script>alert("no es seguro!")</script>

Cdigo ejecutable sin escapado como este (por ejemplo, ingresado en el cuerpo de un
comentario de un blog) no es seguro, porque puede ser usado para generar un ataque de tipo
Cross Site Scripting (XSS) en perjuicio de otros usuarios de la pgina.
El ayudante XML puede sanear (sanitize) nuestro texto para prevenir inyecciones y escapar
todas las etiquetas excepto aquellas que explcitamente permitas. Aqu hay un ejemplo:
>>> print XML('<script>alert("no es seguro!")</script>', sanitize=True)
&lt;script&gt;alert(&quot;no es seguro!&quot;)&lt;/script&gt;

El constructor de XML , por defecto, considera los contenidos de algunas etiquetas y algunos
de sus atributos como seguros. Puedes modificar los valores por defecto con los argumentos

opcionales permitted_tags y allowed_attributes . Estos son los valores por defecto de los
argumentos opcionales para el ayudante XML .
XML(text, sanitize=False,
permitted_tags=['a', 'b', 'blockquote', 'br/', 'i', 'li',
'ol', 'ul', 'p', 'cite', 'code', 'pre', 'img/'],
allowed_attributes={'a':['href', 'title'],
'img':['src', 'alt'], 'blockquote':['type']})

Ayudantes incorporados
A

Este ayudante se usa para generar vnculos (links).


>>> print A('<haz clic>', XML('<b>aqu</b>'),
_href='http://www.web2py.com')
<a href='http://www.web2py.com'>&lt;haz clic&gt;<b>aqu</b></a>

En lugar de _href puedes pasar el URL usando el argumento callback . Por ejemplo en una
vista:
{{=A('haz clic aqu', callback=URL('miaccion'))}}

y el resultado de hacer clic en el link ser una llamada ajax a "miaccion" en lugar de una
redireccin.
En este caso, opcionalmente se pueden especificar dos argumentos ms: target y delete :
{{=A('clic aqu', callback=URL('miaccion'), target="t")}}
<div id="t"><div>

y la respuesta de la llamada ajax ser almacenada en el DIV con el id igual a "t".


<div id="b">{{=A('clic aqu', callback=URL('miaccion'), delete='div#b")}}</div>

y al recibir la respuesta, la etiqueta ms prxima que coincida con "div#b" se eliminar. En


este caso, el botn ser eliminado. Una aplicacin tpica es:
{{=A('clic aqu', callback=URL('miaccion'), delete='tr")}}

en una tabla. Presionando el botn se ejecutar el callback y se eliminar el registro de la


tabla. Se pueden combinar callback y delete
El ayudante A toma un argumento especial llamado cid . Funciona de la siguiente forma:
{{=A('pgina del link', _href='http://example.com', cid='miid')}}
<div id="miid"></div>

y hacer clic en el link hace que el contenido se cargue en el div. Esto es parecido pero ms
avanzado que la sintaxis de arriba ya que se ha diseado para que refresque los componentes
de la pgina. Las aplicaciones de cid se tratarn con ms detalle en el captulo 12, en
relacin con el concepto de componente.
Estas funcionalidades ajax requieren jQuery y "static/js/web2py.js", que se incluyen
automticamente al agregar {{'web2py_ajax.html'}} la seccin head de la plantilla general
(layout). "views/web2py_ajax.html" define algunas variables basadas en request e incluye
todos los archivos js y css necesarios.
B

Este ayudante hace que su contenido sea en negrita.


>>> print B('<hola>', XML('<i>mundo</i>'), _class='prueba', _id=0)
<b id="0" class="prueba">&lt;hola&gt;<i>mundo</i></b>

BODY

Este ayudante crea el cuerpo de una pgina.


>>> print BODY('<hola>', XML('<b>mundo</b>'), _bgcolor='red')
<body bgcolor="red">&lt;hola&gt;<b>mundo</b></body>

BR

Este ayudante crea un salto de lnea.


>>> print BR()
<br />

Ten en cuenta que los ayudantes se pueden repetir utilizando el operador de multiplicacin:

>>> print BR()*5


<br /><br /><br /><br /><br />

CAT

Este es un ayudante que realiza concatenacin de otros ayudantes, anlogo a TAG[].


>>> print CAT('Aqu tenemos ', A('link', _href=URL()), ', y aqu hay un poco de ', B('texto en
negrita'), '.')
Aqu tenemos <a href="/app/default/index">link</a>, y aqu hay un poco de <b>texto en
negrita</b>.

CENTER

Este ayudante centra su contenido.


>>> print CENTER('<hola>', XML('<b>mundo</b>'),
>>>

_class='prueba', _id=0)

<center id="0" class="prueba">&lt;hola&gt;<b>mundo</b></center>

CODE

Este ayudante resalta la sintaxis para Python, C, C++, HTML y cdigo web2py, y se
recomienda en lugar de PRE para muestras de cdigo. CODE tambin tiene la funcionalidad
de crear links a la documentacin de la API de web2py.
Este es un ejemplo de resaltado de una seccin de cdigo fuente Python.
>>> print CODE('print "hola"', language='python').xml()
<table><tr valign="top"><td style="width:40px; text-align: right;"><pre style="
font-size: 11px;
font-family: Bitstream Vera Sans Mono,monospace;
background-color: transparent;
margin: 0;
padding: 5px;
border: none;
background-color: #E0E0E0;

color: #A0A0A0;
">1.</pre></td><td><pre style="
font-size: 11px;
font-family: Bitstream Vera Sans Mono,monospace;
background-color: transparent;
margin: 0;
padding: 5px;
border: none;
overflow: auto;
"><span style="color:#185369; font-weight: bold">print </span>
<span style="color: #FF9966">"hola"</span></pre></td></tr>
</table>

Aqu hay un ejemplo similar para HTML


>>> print CODE(
>>> '<html><body>{{=request.env.remote_add}}</body></html>',
>>> language='html')
<table>...<code>...
<html><body>{{=request.env.remote_add}}</body></html>
...</code>...</table>

Estos son los argumentos por defecto para el ayudante CODE :


CODE("print 'hola mundo'", language='python', link=None, counter=1, styles={})

Los valores soportados para el argumento language son "python", "html_plain", "c", "cpp",
"web2py", y "html". El lenguaje interpreta etiquetas {{ y }} como cdigo "web2py", mientras que
"html_plain" no lo hace.
Si se especifica un valor link , por ejemplo "/examples/global/vars/", las referencias a la API de
web2py en el cdigo se vinculan con la documentacin en el URL del link. Por ejemplo,
"request" se asociara a "/examples/global/vars/request". En el ejemplo de arriba, el URL del
link es manejado por la accin "vars" en el controlador "global.py" que se distribuye como
parte de la aplicacin "examples" de web2py.

El argumento counter se usa para numeracin de lneas. Se puede establecer segn tres
opciones. Puede ser None para omitir nmeros de lnea, un valor numrico indicando el
nmero inicial, o una cadena. Si el contador se especifica con una cadena, es interpretado
como un smbolo (prompt), y no se crean nmeros de lnea.
El argumento styles es un poco complicado. Si observas el HTML generado arriba, notars
que contiene una tabla con dos columnas, y cada columna tiene su propio estilo declarado por
lnea utilizando CSS. Los atributos style te dan la posibilidad de sobrescribir esos estilos
CSS. Por ejemplo:
{{=CODE(...,styles={'CODE':'margin: 0;padding: 5px;border: none;'})}}

El atributo styles debe ser un diccionario, y permite dos posibles valores: CODE para el estilo
del cdigo en si, y LINENUMBERS para el estilo de la columna izquierda, que contiene los
nmeros de lnea. Ten en cuenta que estos estilos no se agregan a los estilos existentes, sino
que los sustituyen.
COL
>>> print COL('a','b')
<col>ab</col>

COLGROUP
>>> print COLGROUP('a','b')
<colgroup>ab</colgroup>

DIV

Todos los ayudantes a excepcin de XML derivan de DIV y heredan sus mtodos bsicos.
>>> print DIV('<hola>', XML('<b>mundo</b>'), _class='prueba', _id=0)
<div id="0" class="prueba">&lt;hola&gt;<b>mundo</b></div>

EM

Enfatiza su contenido
>>> print EM('<hola>', XML('<b>mundo</b>'), _class='prueba', _id=0)
<em id="0" class="prueba">&lt;hola&gt;<b>mundo</b></em>

FIELDSET

Esto se utiliza para crear un campo de carga de datos (input) junto con su etiqueta (label).
>>> print FIELDSET('Altura:', INPUT(_name='altura'), _class='prueba')
<fieldset class="prueba">Alto:<input name="altura" /></fieldset>

FORM

Este es uno de los ayudantes ms importantes. Es un simple formulario, sencillamente crea


una etiqueta <form>...</form> , pero como los ayudantes son objetos y tienen control de su
contenido, pueden procesar formularios enviados (por ejemplo, realizar validacin de los
campos). Esto se tratar en detalle en el captulo 7.
>>> print FORM(INPUT(_type='submit'), _action='', _method='post')
<form enctype="multipart/form-data" action="" method="post">
<input type="submit" /></form>

El "enctype" es "multipart/form-data" por defecto.


El constructor de un FORM , y el de un SQLFORM , puede adems tomar un argumento
llamado hidden . Cuando un diccionario se pasa como hidden (oculto), sus tem son
traducidos como campos INPUT de tipo "hidden". Por ejemplo:
>>> print FORM(hidden=dict(a='b'))
<form enctype="multipart/form-data" action="" method="post">
<input value="b" type="hidden" name="a" /></form>

H1, H2, H3, H4, H5, H6

Estos ayudantes son para los ttulos de prrafos y encabezados menores:


>>> print H1('<hola>', XML('<b>mundo</b>'), _class='prueba', _id=0)
<h1 id="0" class="prueba">&lt;hola&gt;<b>mundo</b></h1>

HEAD

Para crear la etiqueta HEAD de una pgina HTML.


>>> print HEAD(TITLE('<hola>', XML('<b>mundo</b>')))

<head><title>&lt;hola&gt;<b>mundo</b></title></head>

HTML

Este ayudante es un tanto diferente. Adems de crear las etiquetas <html> , configura la
etiqueta con una cadena doctype [xhtml-w,xhtml-o,xhtml-school] .
>>> print HTML(BODY('<hola>', XML('<b>mundo</b>')))
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html><body>&lt;hola&gt;<b>mundo</b></body></html>

El ayudante HTML tambin recibe algunos argumentos opcionales que tienen los siguientes
valores por defecto:
HTML(..., lang='en', doctype='transitional')

donde doctype puede ser 'strict', 'transitional', 'frameset', 'html5', o una cadena doctype
completa.
XHTML

XHTML es similar a HTML pero en cambio crea un doctype XHTML.


XHTML(..., lang='en', doctype='transitional', xmlns='http://www.w3.org/1999/xhtml')

donde doctype puede ser 'strict', 'transitional', 'frameset', o una cadena doctype completa.
HR

Este ayudante crea una lnea horizontal en la pgina


>>> print HR()
<hr />

Este ayudante hace que el contenido sea en letra inclinada (italic).


>>> print I('<hola>', XML('<b>mundo</b>'), _class='prueba', _id=0)
<i id="0" class="prueba">&lt;hola&gt;<b>mundo</b></i>

INPUT

Crea una etiqueta <input.../> . Una etiqueta input puede no contener otras etiquetas y es
cerrada por /> en lugar de > . La etiqueta input tiene un atributo opcional _type que se
puede establecer como "text" (por defecto), "submit", "checkbox" o "radio".
>>> print INPUT(_name='prueba', _value='a')
<input value="a" name="prueba" />

Adems toma un argumento opcional especial llamado "value", distinto de "_value". El ltimo
establece el valor por defecto para el campo input; el primero establece su valor actual. Para
un input de tipo "text", el primero sobrescribe al segundo:
>>> print INPUT(_name='prueba', _value='a', value='b')
<input value="b" name="prueba" />

Para los botones tipo radio, INPUT establece el valor "checked" segn la seleccin:
>>> for v in ['a', 'b', 'c']:
>>>

print INPUT(_type='radio', _name='prueba', _value=v, value='b'), v

<input value="a" type="radio" name="prueba" /> a


<input value="b" type="radio" checked="checked" name="prueba" /> b
<input value="c" type="radio" name="prueba" /> c

y en forma similar para los checkbox:


>>> print INPUT(_type='checkbox', _name='prueba', _value='a', value=True)
<input value="a" type="checkbox" checked="checked" name="prueba" />
>>> print INPUT(_type='checkbox', _name='prueba', _value='a', value=False)
<input value="a" type="checkbox" name="prueba" />

IFRAME

Este ayudante incluye otra pgina web en la pgina web actual. El url de la otra pgina se
especifica por el atributo "_src".
>>> print IFRAME(_src='http://www.web2py.com')
<iframe src="http://www.web2py.com"></iframe>

IMG

Se puede usar para embeber imgenes en HTML:


>>> IMG(_src='http://example.com/image.png',_alt='prueba')
<img src="http://example.com/image.png" alt="prueba" />

Aqu hay una combinacin de los ayudantes A, IMG y URL para incluir una imagen esttica
con un link:
>>> A(IMG(_src=URL('static','logo.png'), _alt="Mi Logo"),
_href=URL('default','index'))
<a href="/myapp/default/index">
<img src="/myapp/static/logo.png" alt="Mi Logo" />
</a>

LABEL

Se usa para crear una etiqueta LABEL para un campo INPUT.


>>> print LABEL('<hola>', XML('<b>mundo</b>'), _class='prueba', _id=0)
<label id="0" class="prueba">&lt;hola&gt;<b>mundo</b></label>

LEGEND

Se usa para crear una etiqueta legend para un campo form.


>>> print LEGEND('Name', _for='micampo')
<legend for="micampo">Name</legend>

LI

Crea un tem de lista y debera incluirse en una etiqueta UL o OL .


>>> print LI('<hola>', XML('<b>mundo</b>'), _class='prueba', _id=0)
<li id="0" class="prueba">&lt;hola&gt;<b>mundo</b></li>

META

Se utiliza para crear etiquetas META en el encabezado HTML . Por ejemplo:

>>> print META(_name='seguridad', _content='alta')


<meta name="seguridad" content="alta" />

MARKMIN

Implementa la sintaxis de wiki markmin. Convierte el texto de entrada en salida html segn las
reglas de markmin descriptas en el ejemplo que sigue:
>>> print MARKMIN("esto es en **negrita** o ''inclinada'' y este es [[un link
http://web2py.com]]")
<p>esto es en <b>negrita</b> o <i>inclinada</i> y
este es <a href="http://web2py.com">un link</a></p>

La sintaxis markmin se describe en este archivo incluido con web2py:


http://127.0.0.1:8000/examples/static/markmin.html

Puedes usar markmin para generar documentos HTML, LaTex y PDF:


m = "hola **mundo** [[link http://web2py.com]]"
from gluon.contrib.markmin.markmin2html import markmin2html
print markmin2html(m)
from gluon.contrib.markmin.markmin2latex import markmin2latex
print markmin2latex(m)
from gluon.contrib.markmin.markmin2pdf import markmin2pdf
print markmin2pdf(m) # requiere pdflatex

(el ayudante MARKMIN es un atajo para markmin2html )


Esta es una pequea introduccin a la sintaxis:

CDIGO

SALIDA

# ttulo

ttulo

## seccin

seccin

### subseccin

subseccin

**negrita**

negrita

''inclinada''

inclinada

``verbatim``

verbatim

http://google.com

http://google.com

http://...

<a href="http://...">http:...</a>

http://...png

<img src="http://...png" />

http://...mp3

<audio src="http://...mp3"></audio>

http://...mp4

<video src="http://...mp4"></video>

qr:http://...

<a href="http://..."><img src="qr cdigo"/></a>

embed:http://...

<iframe src="http://..."></iframe>

[[clic aqu #mianchor]]

clic aqu

$$\int_a^b sin(x)dx$$

Simplemente incluyendo un link a una imagen, video o archivo de audio sin etiquetas produce
la correspondiente archivo de imagen, video o audio incluido automticamente (para audio y
video usa las etiquetas html <audio> y <video>).
Si se agrega un link con el prefijo qr: como por ejemplo
qr:http://web2py.com

hace que se embeba el correspondiente cdigo QR y que enlace al URL.

Si se agrega el link con el prefijo embed: de esta forma:


embed:http://www.youtube.com/embed/x1w8hKTJ2Co

hace que la pgina se embeba, en este caso se embebe un video de youtube.


Las imgenes tambin se pueden embeber con la siguiente sintaxis:
[[descripcin-de-la-imagen http://.../imagen.png right 200px]]

Listas sin orden con:


- one
- two
- three

Listas ordenadas con:


+ one
+ two
+ three

y tablas con:
---------X|0|0
0|X|0
0|0|1
----------

La sintaxis MARKMIN adems contempla blockquote, etiquetas de audio y video HTML5,


alineamiento de imgenes, css personalizado, y se puede extender:
MARKMIN("``abab``:custom", extra=dict(custom=lambda text: text.replace('a','c'))

crea
'cbcb'

Los bloques personalizados se delimitan con ``...``:<clave> y se procesan en la funcin


pasada como valor para la clave correspondiente en el argumento extra (diccionario) de
MARKMIN. Ten en cuenta que la funcin puede necesitar escapar la salida para prevenir XSS.
OBJECT

Se usa para embeber objetos (por ejemplo, un reproductor de flash) en el HTML.


>>> print OBJECT('<hola>', XML('<b>mundo</b>'),
>>>

_src='http://www.web2py.com')

<object src="http://www.web2py.com">&lt;hola&gt;<b>mundo</b></object>

OL

Significa Listas Ordenadas. La lista debera contener etiquetas LI. Los argumentos de OL que
no son LI se encierran automticamente en etiquetas <li>...</li> .
>>> print OL('<hola>', XML('<b>mundo</b>'), _class='prueba', _id=0)
<ol id="0" class="prueba"><li>&lt;hola&gt;</li><li><b>mundo</b></li></ol>

ON

Esto se incluye para compatibilidad hacia atrs y es un simple alias para True . Se usa
exclusivamente para los checkbox y se ha deprecado ya que True es ms pythnico.
>>> print INPUT(_type='checkbox', _name='prueba', _checked=ON)
<input checked="checked" type="checkbox" name="prueba" />

OPTGROUP

Te permite agrupar mltiples opciones en un SELECT y es til para personalizar los campos
usando CSS.
>>> print SELECT('a', OPTGROUP('b', 'c'))
<select>
<option value="a">a</option>
<optgroup>
<option value="b">b</option>
<option value="c">c</option>

</optgroup>
</select>

OPTION

Esto se debera usar nicamente como parte de una combinacin SELECT/OPTION.


>>> print OPTION('<hola>', XML('<b>mundo</b>'), _value='a')
<option value="a">&lt;hola&gt;<b>mundo</b></option>

Como en el caso de INPUT , web2py distingue entre "_value" (el valor de la opcin OPTION) y
"value" (el valor actual del SELECT). Si son iguales, la opcin es "selected".
>>> print SELECT('a', 'b', value='b'):
<select>
<option value="a">a</option>
<option value="b" selected="selected">b</option>
</select>

Se usa para crear un prrafo.


>>> print P('<hola>', XML('<b>mundo</b>'), _class='prueba', _id=0)
<p id="0" class="prueba">&lt;hola&gt;<b>mundo</b></p>

PRE

Crea una etiqueta <pre>...</pre> para mostrar texto preformateado (pre-formatted). El


ayudante CODE es en general preferible para muestras de cdigo fuente (code listings).
>>> print PRE('<hola>', XML('<b>mundo</b>'), _class='prueba', _id=0)
<pre id="0" class="prueba">&lt;hola&gt;<b>mundo</b></pre>

SCRIPT

Esto incluye o "linkea" un script, como por ejemplo JavaScript. El contenido entre etiquetas se
genera como comment de HTML, para compatibilidad con navegadores muy antiguos.

>>> print SCRIPT('alert("hola mundo");', _type='text/javascript')


<script type="text/javascript"><!-alert("hola mundo");
//--></script>

SELECT

Crea una etiqueta <select>...</select> . Esto se usa para el ayudante OPTION . Los
argumentos SELECT que no sean objetos OPTION se convierten automticamente a option.
>>> print SELECT('<hola>', XML('<b>mundo</b>'), _class='prueba', _id=0)
<select id="0" class="prueba">
<option value="&lt;hola&gt;">&lt;hola&gt;</option>
<option value="&lt;b&gt;mundo&lt;/b&gt;"><b>mundo</b></option>
</select>

SPAN

Parecido a DIV pero se usa para contenido tipo inline (en lugar de block).
>>> print SPAN('<hola>', XML('<b>mundo</b>'), _class='prueba', _id=0)
<span id="0" class="prueba">&lt;hola&gt;<b>mundo</b></span>

STYLE

Parecido a script, pero se usa para o bien incluir o hacer un link de cdigo CSS. Este es el
CSS incluido:
>>> print STYLE(XML('body {color: white}'))
<style><!-body { color: white }
//--></style>

en cambio aqu se "linkea" (linked code):


>>> print STYLE(_src='style.css')
<style src="style.css"><!--

//--></style>

TABLE, TR, TD

Estas etiquetas (junto con los ayudantes opcionales THEAD , TBODY y TFOOTER ) se usan
para armar tablas HTML.
>>> print TABLE(TR(TD('a'), TD('b')), TR(TD('c'), TD('d')))
<table><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>
TR espera

contenido TD ;

los

argumentos

que

no

son

objetos TD se

convierten

automticamente.
>>> print TABLE(TR('a', 'b'), TR('c', 'd'))
<table><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>

Es fcil convertir una lista de Python en una tabla HTML usando la notacin * para
argumentos de funciones, que asocia elementos de una lista con argumentos de funcin
posicionales (positional function arguments).
Aqu, lo hacemos lnea por lnea:
>>> table = [['a', 'b'], ['c', 'd']]
>>> print TABLE(TR(*table[0]), TR(*table[1]))
<table><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>

Aqu, en cambio, usamos todas las lneas de una vez:


>>> table = [['a', 'b'], ['c', 'd']]
>>> print TABLE(*[TR(*rows) for rows in table])
<table><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>

TBODY

Esto se usa para crear etiquetas de registros de tabla contenidos en el cuerpo de la tabla, no
para registros incluidos en el encabezado o pie de la tabla. Esto es opcional.
>>> print TBODY(TR('<hola>'), _class='prueba', _id=0)
<tbody id="0" class="prueba"><tr><td>&lt;hola&gt;</td></tr></tbody>

TEXTAREA

Este ayudante crea una etiqueta <textarea>...</textarea> .


>>> print TEXTAREA('<hola>', XML('<b>mundo</b>'), _class='prueba')
<textarea class="prueba" cols="40" rows="10">&lt;hola&gt;<b>mundo</b></textarea>

El nico detalle es que su atributo "value" opcional sobrescribe su contenido (HTML interno)
>>> print TEXTAREA(value="<hola mundo>", _class="prueba")
<textarea class="prueba" cols="40" rows="10">&lt;hola mundo&gt;</textarea>

TFOOT

Esto se usa para crear etiquetas para registros de pie de tabla.


>>> print TFOOT(TR(TD('<hola>')), _class='prueba', _id=0)
<tfoot id="0" class="prueba"><tr><td>&lt;hola&gt;</td></tr></tfoot>

TH

Usado para los encabezados de tabla en lugar de TD .


>>> print TH('<hola>', XML('<b>mundo</b>'), _class='prueba', _id=0)
<th id="0" class="prueba">&lt;hola&gt;<b>mundo</b></th>

THEAD

Se usa para los registros de encabezado de tabla.


>>> print THEAD(TR(TH('<hola>')), _class='prueba', _id=0)
<thead id="0" class="prueba"><tr><th>&lt;hola&gt;</th></tr></thead>

TITLE

Usado para crear etiquetas de ttulo de pgina en el encabezado HTML.


>>> print TITLE('<hola>', XML('<b>mundo</b>'))
<title>&lt;hola&gt;<b>mundo</b></title>

TR

Etiqueta un registro de tabla. se debera generar dentro de una tabla y contener


etiquetas <td>...</td> . Los argumentos TR que no son objetos TD se convierten
automticamente.
>>> print TR('<hola>', XML('<b>mundo</b>'), _class='prueba', _id=0)
<tr id="0" class="prueba"><td>&lt;hola&gt;</td><td><b>mundo</b></td></tr>

TT

Etiqueta un texto como monoespaciado o de mquina de escribir.


>>> print TT('<hola>', XML('<b>mundo</b>'), _class='prueba', _id=0)
<tt id="0" class="prueba">&lt;hola&gt;<b>mundo</b></tt>

UL

Indica una lista sin orden y debera contener elementos LI. Si el contenido no se etiqueta como
LI, UL lo hace automticamente.
>>> print UL('<hola>', XML('<b>mundo</b>'), _class='prueba', _id=0)
<ul id="0" class="prueba"><li>&lt;hola&gt;</li><li><b>mundo</b></li></ul>

embed64
embed64(filename=None,

file=None,

data=None,

extension='image/gif') codifica

la

informacin en formato binario en base64.


filename: si se especifica, abre y lee el archivo en modo 'rb'. file: si se especifica, lee el
archivo. data: si se provee, usa los datos ingresados.
xmlescape
xmlescape(data, quote=True) devuelve una cadena escapada con los datos ingresados.
>>> print xmlescape('<hola>')
&lt;hola&gt;

Ayudantes personalizados
TAG

A veces se debe generar etiquetas personalizadas XML. web2py incorpora TAG , un


generador universal de etiquetas.
{{=TAG.name('a', 'b', _c='d')}}

crea el siguiente XML


<name c="d">ab</name>

Los argumentos "a", "b", y "d" se escapan automticamente; usa el ayudante XML para
suprimir este comportamiento. Usando TAG puedes crear etiquetas HTML/XML que no son
provistas por la API por defecto. Las TAG se pueden anidar, y se serializan con str() . Una
sintaxis equivalente es:
{{=TAG['name']('a', 'b', c='d')}}

Si el objeto TAG se crea con un nombre vaco, se puede usar para concatenar cadenas
mltiples junto con ayudantes HTML sin incorporarlos en una etiqueta envolvente, pero esa
tcnica esta deprecada. En su lugar, usa el ayudante CAT .
Nota que TAG es un objeto, y TAG.name o TAG['name'] es una funcin que devuelve una
clase de ayudante temporaria.
MENU

El ayudante MENU toma una lista de listas o de tuplas con el formato


de response.menu (segn se describe en el captulo 4) y crea una estructura de rbol
utilizando listas sin orden para mostrar el men. Por ejemplo:
>>> print MENU([['Uno', False, 'link1'], ['Dos', False, 'link2']])
<ul class="web2py-menu web2py-menu-vertical">
<li><a href="link1">Uno</a></li>
<li><a href="link2">Dos</a></li>
</ul>

El tercer tem en cada lista/tupla puede ser un ayudante HTML (que podra incluir
ayudantes anidados), y el ayudante MENU simplemente convertir ese ayudante en lugar
de crear su propia etiqueta <a> .
Cada tem de men puede tener un cuarto argumento que consiste de un submen anidado (y
del mismo modo para todo tem en forma recursiva):

>>> print MENU([['Uno', False, 'link1', [['Dos', False, 'link2']]]])


<ul class="web2py-menu web2py-menu-vertical">
<li class="web2py-menu-expand">
<a href="link1">Uno</a>
<ul class="web2py-menu-vertical">
<li><a href="link2">Dos</a></li>
</ul>
</li>
</ul>

Un tem de men puede tambin tener un quinto elemento opcional, que es un valor booleano.
Cuando es falso, el tem de men es ignorado por el ayudante MENU.
El ayudante MENU toma los siguientes argumentos opcionales:
o

_class : por defecto es "web2py-menu web2py-menu-vertical" y establece la clase de

los elementos UL externos.


o

ul_class : por defecto "web2py-menu-vertical" y establece la clase de los UL internos.

li_class : por defecto "web2py-menu-expand" y establece la clase de los elementos LI

internos.
o

li_first : permite agregar una clase al primer elemento de la lista.

li_last : permite agregar una clase al ltimo elemento de la lista.

MENU toma un argumento opcional mobile . Cuando se especifica True , en lugar de

construir

una

estructura

de

men UL en

forma

recursiva

devuelve

una

lista

desplegable SELECT con todas las opciones del men y un atributo onchange que redirige a
la pgina correspondiente a la opcin seleccionada. Esto est diseado como representacin
de men alternativa para una mayor usabilidad en pequeos dispositivos mviles como por
ejemplo en telfonos.
Usualmente el men se usa en una plantilla con la siguiente sintaxis:
{{=MENU(response.menu, mobile=request.user_agent().is_mobile)}}

De esta forma un dispositivo mvil se detecta automticamente y se crea un men compatible.

BEAUTIFY
BEAUTIFY se usa para construir HTML a partir de objetos compuestos, incluyendo listas,

tuplas y diccionarios:
{{=BEAUTIFY({"a": ["hola", XML("mundo")], "b": (1, 2)})}}
BEAUTIFY devuelve un objeto tipo XML serializable en XML, con una representacin elegante

del argumento de su constructor. Para este caso, la representacin en XML de:


{"a": ["hola", XML("mundo")], "b": (1, 2)}

se procesar como:
<table>
<tr><td>a</td><td>:</td><td>hola<br />mundo</td></tr>
<tr><td>b</td><td>:</td><td>1<br />2</td></tr>
</table>

DOM'' en el servidor y parseado


elements

El ayudante DIV y todos sus


bsqueda element y elements .

ayudantes

derivados

proveen

de

mtodos

de

element devuelve el primer elemento hijo que coincida con la condicin especificada (o None

si no hay coincidencias).
elements devuelve una lista de todos los elementos hijo encontrados que cumplen con la

condicin.
element and elements usan la misma sintaxis para especificar condiciones de bsqueda, que
permiten tres posibles mtodos combinables: expresiones tipo jQuery, bsqueda por valor
exacto del atributo y bsqueda con expresiones regulares.
Aqu hay un ejemplo sencillo:

>>> a = DIV(DIV(DIV('a', _id='referencia',_class='abc')))


>>> d = a.elements('div#referencia')
>>> d[0][0] = 'changed'
>>> print a
<div><div><div id="referencia" class="abc">changed</div></div></div>

El argumento sin nombre elements es una cadena, que puede contener: el nombre de una
etiqueta, el id de la etiqueta precedido por el signo almohadilla o numeral (#), la clase
precedida por punto (.) o el valor explcito del atributo entre corchetes ([]).
Aqu se muestran 4 formas equivalentes para buscar la etiqueta anterior por id:
>>> d = a.elements('#referencia')
>>> d = a.elements('div#referencia')
>>> d = a.elements('div[id=referencia]')
>>> d = a.elements('div',_id='referencia')

Estos son 4 formas equivalentes para buscar la etiqueta anterior por clase:
>>> d = a.elements('.abc')
>>> d = a.elements('div.abc')
>>> d = a.elements('div[class=abc]')
>>> d = a.elements('div',_class='abc')

Todo atributo se puede usar para ubicar un elemento (no slo id y clase ), incluso los
atributos mltiples (el mtodo element puede tomar mltiples pares nombre-valor), pero slo
se devuelve el primer elemento encontrado.
Si se usa la sintaxis de jQuery "div#referencia" es posible especificar mltiples criterios de
bsqueda separados por un espacio:
>>> a = DIV(SPAN('a', _id='t1'), DIV('b', _class='c2'))
>>> d = a.elements('span#t1, div.c2')

o el equivalente
>>> a = DIV(SPAN('a', _id='t1'), DIV('b', _class='c2'))

>>> d = a.elements('span#t1', 'div.c2')

Si el valor de un atributo se especifica usando un par nombre-valor como argumento, puede


ser una cadena o expresin regular:
>>> a = DIV(SPAN('a', _id='prueba123'), DIV('b', _class='c2'))
>>> d = a.elements('span', _id=re.compile('prueba\d{3}')

Hay un argumento de par nombre-valor especial de los ayudantes DIV (y derivados)


llamado find . Se puede usar para especificar un valor de bsqueda o una expresin regular
de bsqueda en el texto contenido por la etiqueta. Por ejemplo:
>>> a = DIV(SPAN('abcde'), DIV('fghij'))
>>> d = a.elements(find='bcd')
>>> print d[0]
<span>abcde</span>

o
>>> a = DIV(SPAN('abcde'), DIV('fghij'))
>>> d = a.elements(find=re.compile('fg\w{3}'))
>>> print d[0]
<div>fghij</div>

components

Este es un ejemplo de cmo listar los elementos en una cadena de html:


html = TAG('<a>xxx</a><b>yyy</b>')
for item in html.components: print item

parent

y siblings

parent devuelve el padre del elemento actual.


>>> a = DIV(SPAN('a'),DIV('b'))
>>> s = a.element('span')
>>> d = s.parent

>>> d['_class']='abc'
>>> print a
<div class="abc"><span>a</span><div>b</div></div>
>>> for e in s.siblings(): print e
<div>b</div>

Reemplazo de elementos
Los elementos encontrados se pueden reemplazar o eliminar especificando el
argumento replace . Observa que igualmente se devuelve una lista de los elementos
encontrados.
>>> a = DIV(SPAN('x'), DIV(SPAN('y'))
>>> b = a.elements('span', replace=P('z')
>>> print a
<div><p>z</p><div><p>z</p></div>
replace puede ser un callable. En ese caso se pasar al elemento original y se espera que

devuelva el elemento sustituto:


>>> a = DIV(SPAN('x'), DIV(SPAN('y'))
>>> b = a.elements('span', replace=lambda t: P(t[0])
>>> print a
<div><p>x</p><div><p>y</p></div>

Si se cumple replace=None , los elementos que coincidan se eliminan por completo.


>>> a = DIV(SPAN('x'), DIV(SPAN('y'))
>>> b = a.elements('span', replace=None)
>>> print a
<div></div>

flatten

El mtodo flatten serializa en forma recursiva el contenido de los hijos de un determinado


elemento en texto normal (sin etiquetas):

>>> a = DIV(SPAN('esta', DIV('es', B('una'))), SPAN('prueba'))


>>> print a.flatten()
estaesunaprueba

Flatten recibe un argumento opcional, render , por ejemplo una funcin que convierte/aplana
(flatten) el contenido usando un protocolo distinto. Aqu se muestra un ejemplo para serializar
algunas etiquetas en la sintaxis de wiki markmin:
>>> a = DIV(H1('ttulo'), P('ejemplo de un ', A('link', _href='#prueba')))
>>> from gluon.html import markmin_serializer
>>> print a.flatten(render=markmin_serializer)

## ttulo

ejemplo de [[un link #prueba]]

Al momento de esta edicin disponemos de markmin_serializer y markdown_serializer .

Parseado (parsing)
El objeto TAG es tambin un parseador XML/HTML. Puede leer texto y convertirlo en una
estructura de rbol de ayudantes. Esto facilita la manipulacin por medio de la API descripta
arriba:
>>> html = '<h1>Ttulo</h1><p>esta es una <span>prueba</span></p>'
>>> parsed_html = TAG(html)
>>> parsed_html.element('span')[0]='PRUEBA'
>>> print parsed_html
<h1>Ttulo</h1><p>esta es una <span>PRUEBA</span></p>

Diseo de pgina (layout)


Las vistas se pueden extender e incluir otras vistas en una estructura de rbol.
Por ejemplo, podemos pensar en una vista "index.html" que extiende "layout.html" e incluye
"body.html". Al mismo tiempo, "layout.html" puede incluir "header.html" y "footer.html".

La raz del rbol es lo que denominamos vista layout. Como con cualquier otra plantilla HTML,
puedes editar el contenido utilizando la interfaz administrativa de web2py. El archivo
"layout.html" es slo una convencin.
Este es un ejemplo minimalista de pgina que extiende la vista "layout.html" e incluye la vista
"pagina.html":
{{extend 'layout.html'}}
<h1>hola mundo</h1>
{{include 'pagina.html'}}

El archivo de layout extendido debe contener una instruccin {{include}} , algo como:
<html>
<head>
<title>Ttulo de la pgina</title>
</head>
<body>
{{include}}
</body>
</html>

Cuando se llama a la vista, se carga la vista extendida layout, y la vista que hace la llamada
reemplaza la instruccin {{include}} dentro del layout. El procesamiento contina en forma
recursiva hasta que toda instruccin extend e include se haya procesado. La plantilla
resultante es entonces traducida a cdigo Python. Ten en cuenta que cuando una aplicacin
es compilada en bytecode (bytecode compiled), es este cdigo Python lo que se compila, no
los archivos de las vistas originales. De este modo, la versin bytecode compiled de una vista
determinada es un nico archivo .pyc que incluye no slo el cdigo fuente Python de la vista
original, sino el rbol completo de las vistas incluidas y extendidas.
extend

, include , block y super son instrucciones de plantilla especiales, no comandos

de Python.
Todo contenido o cdigo que precede a la instruccin {{extend ...}} se insertar (y por lo
tanto ejecutar) antes de el comienzo del contenido y/o cdigo de una vista extendida. Aunque
en realidad esto no se use tpicamente para insertar contenido HTML antes que el contenido
de la vista extendida, puede ser de utilidad como medio para definir variables o funciones que

quieras que estn disponibles para la vista extendida. Por ejemplo, tomemos como ejemplo la
vista "index.html":
{{sidebar_enabled=True}}
{{extend 'layout.html'}}
<h1>Pgina de inicio</h1>

y una seccin de "layout.html":


{{if sidebar_enabled:}}
<div id="barralateral">
Contenido de la barra lateral
</div>
{{pass}}

Como el sidebar_enabled establecido en "index.html" viene antes que el extend , esa lnea
ser insertada antes del comienzo de "layout.html", haciendo que sidebar_enabled est
disponible en cualquier parte del cdigo "layout.html" (una versin un tanto ms sofisticada de
este ejemplo se utiliza en la app welcome).
Adems es conveniente aclarar que las variables devueltas por la funcin del controlador
estn disponibles no slo en la vista principal de la funcin, sino que estn disponibles en toda
vista incluida o extendida.
El argumento de un extend o include (por ejemplo el nombre de la vista extendida o incluida)
puede ser una variable de Python (pero no una expresin de Python). Sin embargo, esto
impone una limitacin -- las vistas que usan variables en instrucciones extend o include no
pueden ser compiladas con bytecode. Como se mencion ms arriba, las vistas compiladas
con bytecode incluyen todo el rbol de vistas incluidas y extendidas, de manera que las vistas
extendidas e incluidas especficas se deben conocer en tiempo de compilacin, lo cual no es
posible si los nombres de las vistas son variables (cuyos valores no se especifican hasta el
tiempo de ejecucin). Como las vistas compiladas con bytecode pueden proveer de una
notable mejora de la performance, se debera evitar el uso de variables
en extend e include en lo posible.
En algunos casos, una alternativa al uso de una variable en include es usar simples
instrucciones {{include ...}} dentro de un bloque if...else .
{{if una_condicion:}}

{{include 'esta_vista.html'}}
{{else:}}
{{include 'esa_vista.html'}}
{{pass}}

El cdigo anterior no presenta ningn problema respecto de la compilacin con bytecode


porque no hay variables en juego. Ten en cuenta, de todos modos, que la vista compilada con
bytecode en realidad incluir tanto el cdigo Python de "esta_vista.html" como el de
"esa_vista.html", aunque slo el cdigo de una de esas vistas se ejecutar, dependiendo del
valor de una_condicion .
Recuerda

que

esto

slo

funcionar

para include --

no

puedes

poner

instrucciones {{extend ...}} dentro de bloques if...else .


Los diseos (layouts) se usan para encapsular elementos comunes de las pginas
(encabezados, pies, mens), y si bien no son obligatorios, harn que tu aplicacin sea ms
fcil de mantener. En particular, te sugerimos que escribas layouts que aprovechen las
siguientes variables que se pueden establecer en el controlador. El uso de estas variables
especiales har que tus diseos sean intercambiables:
response.title
response.subtitle
response.meta.author
response.meta.keywords
response.meta.description
response.flash
response.menu
response.files

A excepcin de menu y files , se trata de cadenas y su uso debera ser obvio.


El men response.menu es una lista de tuplas con 3 o 4 elementos. Los tres elementos son:
el nombre del link, un booleano que indica si el link est activo (si es el link actual), y el URL
de la pgina linkeada (direccin del vnculo). Por ejemplo:
response.menu = [('Google', False, 'http://www.google.com',[]),
('Inicio', True, URL('inicio'), [])]

El cuarto elemento de la tupla es un submen opcional.


response.files es una lista de archivos CSS y JS requeridos por tu pgina.

Tambin te recomendamos el uso de:


{{include 'web2py_ajax.html'}}

en el encabezado HTML, porque agregar las libreras jQuery y definir algunas funciones
JavaScript con efectos Ajax para compatibilidad hacia atrs. "web2py_ajax.html" incluye las
etiquetas response.meta en la vista, jQuery bsico, la interfaz para seleccin de fecha y todos
los archivos CSS y JS especificados en response.files .

Diseo de pgina por defecto


El diseo "views/layout.html" que incorpora por defecto la aplicacin de
andamiaje welcome (sin mostrar algunas partes opcionales) es bastante complejo pero se
basa en la siguiente estructura:
<!DOCTYPE html>
<head>
<meta charset="utf-8" />
<title>{{=response.title or request.application}}</title>
...
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script>

{{
response.files.append(URL('static','css/web2py.css'))
response.files.append(URL('static','css/bootstrap.min.css'))
response.files.append(URL('static','css/bootstrap-responsive.min.css'))
response.files.append(URL('static','css/web2py_bootstrap.css'))
}}

{{include 'web2py_ajax.html'}}

{{
# para usar las barras laterales debes especificar cul vas a usar
left_sidebar_enabled = globals().get('left_sidebar_enabled',False)
right_sidebar_enabled = globals().get('right_sidebar_enabled',False)
middle_columns = {0:'span12',1:'span9',2:'span6'}[
(left_sidebar_enabled and 1 or 0)+(right_sidebar_enabled and 1 or 0)]
}}

{{block head}}{{end}}
</head>

<body>
<!-- Barra de navegacin ====================================== -->
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="flash">{{=response.flash or ''}}</div>
<div class="navbar-inner">
<div class="container">
{{=response.logo or ''}}
<ul id="navbar" class="nav pull-right">
{{='auth' in globals() and auth.navbar(mode="dropdown") or ''}}
</ul>
<div class="nav-collapse">
{{if response.menu:}}
{{=MENU(response.menu)}}
{{pass}}
</div><!--/.nav-collapse -->
</div>
</div>
</div><!--/top navbar -->

<div class="container">
<!-- Seccin superior (masthead) ============================ -->
<header class="mastheader row" id="header">
<div class="span12">
<div class="page-header">
<h1>
{{=response.title or request.application}}
<small>{{=response.subtitle or ''}}</small>
</h1>
</div>
</div>
</header>

<section id="main" class="main row">


{{if left_sidebar_enabled:}}
<div class="span3 left-sidebar">
{{block left_sidebar}}
<h3>Barra lateral izquierda</h3>
<p></p>
{{end}}
</div>
{{pass}}

<div class="{{=middle_columns}}">
{{block center}}
{{include}}
{{end}}
</div>

{{if right_sidebar_enabled:}}
<div class="span3">
{{block right_sidebar}}
<h3>Barra lateral derecha</h3>
<p></p>
{{end}}
</div>
{{pass}}
</section><!--/main-->

<!-- Pie de pgina ========================================== -->


<div class="row">
<footer class="footer span12" id="footer">
<div class="footer-content">
{{block footer}} <!-- este es el pie por defecto -->
...
{{end}}
</div>
</footer>
</div>

</div> <!-- /container -->

<!-- El javascript
================================================
(Se ubica al final del documento para acelerar
la carga de la pgina) -->
<script src="{{=URL('static','js/bootstrap.min.js')}}"></script>

<script src="{{=URL('static','js/web2py_bootstrap.js')}}"></script>
{{if response.google_analytics_id:}}
<script src="{{=URL('static','js/analytics.js')}}"></script>
<script type="text/javascript">
analytics.initialize({
'Google Analytics':{trackingId:'{{=response.google_analytics_id}}'}
});</script>
{{pass}}
</body>
</html>

Hay algunas funcionalidades de este diseo por defecto que lo hacen muy fcil de usar y
personalizar:
o

Est escrito en HTML5 y usa la librera "modernizr" [modernizr] para compatibilidad hacia
atrs. El layout completo incluye algunas instrucciones condicionales requeridas por IE y
se omitieron para simplificar el ejemplo.

Muestra tanto response.title como response.subtitle que pueden establecerse en el


modelo. Si no se especifican, el layout adopta el nombre de la aplicacin como ttulo.

Incluye el archivo web2py_ajax.html en el encabezado HTML que crea todas las


instrucciones de importacin link y script.

Usa una versin modificada de Bootstrap de Twitter para un diseo ms flexible. Es


compatible con dispositivos mviles y modifica las columnas para que se muestren
correctamente en pantallas pequeas.

Usa "analytics.js" para conectar al servicio Analytics de Google.

El {{=auth.navbar(...)}} muestra una bienvenida al usuario actual y enlaza con las


funciones de auth por defecto como login, logout, register, change password, etc. segn
el contexto. Es un creador de ayudantes (helper factory) y la salida se puede manipular
como con cualquier otro ayudante. Se ubica en un bloque {{try:}}...{{except:pass}} en
caso de que auth no se haya habilitado.

{{=MENU(response.menu)}} muestra la estructura del men como <ul>...</ul> .

{{include}} se reemplaza con el contenido de la vista que extiende el diseo cuando

se realiza la conversin (render) de la pgina.


o

Por defecto utiliza una estructura condicional de tres columnas (las barras laterales
derecha e izquierda se pueden deshabilitar en las vistas que extienden el diseo o
layout).

Usa las siguientes clases: header, main, footer

Contiene los siguientes bloques: statusbar, left_sidebar, center, right_sidebar, footer.

En las vistas, puedes habilitar o personalizar las barras laterales de esta forma:
{{left_sidebar_enable=True}}
{{extend 'layout.html'}}

Este texto va en el centro

{{block left_sidebar}}
Este texto va en la barra lateral
{{end}}

Personalizacin del diseo por defecto


Es fcil personalizar el diseo de pgina por defecto o layout porque la aplicacin welcome
est basada en Bootstrap de Twitter, que cuenta con una buena documentacin y soporta el
uso de estilos intercambiables (themes). En web2py son cuatro los archivos relevantes en
relacin con el estilo:
o

"css/web2py.css" contiene la hoja de estilo especfica de web2py

"css/bootstrap.min.css" contiene la hoja de estilo CSS de Bootstrap

[bootstrap]

"css/web2py_bootstrap.css" contiene, con modificaciones, algunos parmetros de


estilo de Bootstrap segn los requerimientos de web2py.

"js/bootstrap.min.js" que viene con las libreras para efectos de men, ventanas de
confirmacin emergentes (modal) y paneles.

Para cambiar los colores y las imgenes de fondo, prueba agregando el siguiente cdigo en el
encabezado de layout.html:
<style>
body { background: url('images/background.png') repeat-x #3A3A3A; }
a { color: #349C01; }
.header h1 { color: #349C01; }
.header h2 { color: white; font-style: italic; font-size: 14px;}
.statusbar { background: #333333; border-bottom: 5px #349C01 solid; }
.statusbar a { color: white; }
.footer { border-top: 5px #349C01 solid; }
</style>

Por supuesto, tambin puedes reemplazar por completo los archivos "layout.html" y
"web2py.css" con un diseo propio.

Desarrollo para dispositivos mviles


El diseo layout.html por defecto est diseado para que sea compatible con dispositivos
mviles pero no es suficiente. Uno puede necesitar distintas vistas cuando una pgina es
visitada con un dispositivo mvil.
Para que el desarrollo para mquinas de escritorio y mviles sea ms fcil, web2py incluye el
decorador @mobilize . Este decorador se aplica a las acciones que deberan separar la vista
normal de la mvil. Aqu se demuestra la forma de hacerlo:
from gluon.contrib.user_agent_parser import mobilize
@mobilize
def index():
return dict()

Observa que el decorador se debe importar antes de usarlo en el controlador.


Cuando la funcin "index" se llama desde un navegador comn (con una mquina de
escritorio),
web2py
convertir
el
diccionario
devuelto
utilizando
la
vista
"[controlador]/index.html". Sin embargo, cuando se llame desde un dispositivo mvil, el

diccionario ser convertido por "[controlador]/index.mobile.html". Observa que las vistas para
mvil tienen la extensin "mobile.html".
Como alternativa puedes aplicar la siguiente lgica para que todas las vistas sean compatibles
con dispositivos mviles:
if request.user_agent().is_mobile:
response.view.replace('.html','.mobile.html')

La tarea de crear las vistas "*.mobile.html" est relegada al desarrollador, pero aconsejamos
especialmente el uso del plugin "jQuery Mobile" que lo hace realmente fcil.

Funciones en las vistas


Dado el siguiente diseo "layout.html":
<html>
<body>
{{include}}
<div class="sidebar">
{{if 'mysidebar' in globals():}}{{mysidebar()}}{{else:}}
mi barra lateral por defecto
{{pass}}
</div>
</body>
</html>

y esta vista que lo extiende


{{def mysidebar():}}
Mi nueva barra lateral!!!
{{return}}
{{extend 'layout.html'}}
Hola mundo!!!

Observa que la funcin se ha definido antes que la instruccin {{extend...}} -- esto hace que
la funcin se cree antes que la ejecucin del cdigo en "layout.html", y de esa forma se puede
llamar a la funcin en el mbito de "layout.html", incluso antes que {{include}} . Ten en
cuenta que la funcin se incluye en la vista extendida sin el prefijo = .
El cdigo genera la siguiente salida:
<html>
<body>
hola mundo!!!
<div class="sidebar">
mi nueva barra lateral!!!
</div>
</body>
</html>

Observa que la funcin se ha definido en HTML (aunque puede tambin contener cdigo
Python) de forma que response.write se usa para escribir su contenido (la funcin no
devuelve el contenido). Es por eso que las celdas del diseo llaman a la funcin
usando {{mysidebar()}} en lugar de {{mysidebar()}} . Las funciones definidas de esta forma
pueden recibir argumentos.

Bloques en las vistas


La forma principal para hacer que una vista sea ms modular es utilizando los {{block...}} y
su mecanismo es una alternativa al mecanismo descripto en la seccin previa.
Si tenemos el siguiente "layout.html":
<html>
<body>
{{include}}
<div class="sidebar">
{{block mysidebar}}
mi barra lateral por defecto

{{end}}
</div>
</body>
</html>

y esta vista que lo extiende


{{extend 'layout.html'}}
Hola mundo!!!
{{block mysidebar}}
Mi nueva barra lateral!!!
{{end}}

Genera el la siguiente salida:


<html>
<body>
Hola mundo!!!
<div class="sidebar">
Mi nueva barra lateral!!!
</div>
</body>
</html>

Puedes tener varios bloques o blocks, y si un bloque est declarado en la vista extendida pero
no en la vista que la extiende, se utiliza el contenido de la vista extendida. Adems, observa
que a diferencia del uso de funciones, no es necesario definir bloques antes
de {{extend ...}} -- incluso si se han definido despus del extend , se pueden usar para
hacer sustituciones en cualquier parte de la vista extendida.
Dentro de un bloque, puedes usar la expresin {{super}} para incluir el contenido de la vista
superior. Por ejemplo, si reemplazamos la siguiente vista que extiende el diseo con:
{{extend 'layout.html'}}
Hola mundo!!!

{{block mysidebar}}
{{super}}
Mi nueva barra lateral!!!
{{end}}

obtenemos:
<html>
<body>
hola mundo!!!
<div class="sidebar">
Mi barra lateral por defecto
Mi nueva barra lateral!!!
</div>
</body>
</html>

La capa de abstraccin de la base de datos


Dependencias
web2py viene con una Capa de Abstraccin de la Base de Datos (DAL), una API que asocia
objetos Python a objetos de la base de datos como consultas, tablas y registros. La DAL
genera dinmicamente el SQL en tiempo real usando el dialecto especfico para la base de
datos utilizada, de forma que no tengas que escribir cdigo SQL o necesites aprender distintos
dialectos para los comandos SQL (usamos aqu el trmino SQL en general), y para que la
aplicacin sea porttil para los distintos tipos de bases de datos. En la tabla de abajo se
muestra una lista parcial de las bases de datos soportadas. Puedes consultar el sitio web de
web2py y la lista de correo si necesitas otro adaptador. La base de datos NoSQL de Google es
un caso particular y se trata en el Captulo 13.
La distribucin binaria para Windows funciona instantneamente con SQLite y MySQL. La
distribucin binaria para Mac funciona sin configuracin adicional con SQLite. Para usar otro
motor o back-end de base de datos, utiliza la versin de cdigo fuente e instala el controlador
o driver correspondiente a tu base de datos.

Una vez que hayas instalado el controlador adecuado, inicia la versin de cdigo fuente de
web2py, que detectar el nuevo controlador. A continuacin se listan los controladores:
database

drivers (fuente)

SQLite

sqlite3 o pysqlite2 o zxJDBC [zxjdbc] (on Jython)

PostgreSQL

psycopg2 [psycopg2] or pg8000 [pg8000] or zxJDBC [zxjdbc] (en Jython)

MySQL

pymysql [pymysql] o MySQLdb [mysqldb]

Oracle

cx_Oracle [cxoracle]

MSSQL

pyodbc [pyodbc]

FireBird

kinterbasdb [kinterbasdb] o fdb o pyodbc

DB2

pyodbc [pyodbc]

Informix

informixdb [informixdb]

Ingres

ingresdbi [ingresdbi]

Cubrid

cubriddb [cubridb] [cubridb]

Sybase

Sybase [Sybase]

Teradata

pyodbc [Teradata]

SAPDB

sapdb [SAPDB]

MongoDB

pymongo [pymongo]

IMAP

imaplib [IMAP]

sqlite3 , pymysql , pg8000 , y imaplib vienen con web2py. El soporte para MongoDB es

experimental. La opcin IMAP te permite usar DAL para acceder a IMAP.


web2py define las siguientes clases que conforman DAL:
El objeto DAL representa una conexin de la base de datos. Por ejemplo:
db = DAL('sqlite://storage.db')

Table representa una tabla de la base de datos. Las instancias de Table no se crean en forma
directa; DAL.define_table se encarga de crearlas.
db.define_table('mitabla', Field('micampo'))

Los mtodos ms importantes de una tabla son:


.insert , .truncate , .drop , e .import_from_csv_file .

Field representa un campo de la base de datos. Se pueden crear instancias de la clase Field y
usarlas como argumentos de DAL.define_table
Rows de DAL
es el objeto devuelto por un comando select de la base de datos. Se puede definir como una
lista de objetos de registro Row :
registros = db(db.mitabla.micampo!=None).select()

Row contiene los valores del registro.


for registro in registros:
print registro.micampo

Query es un objeto que representa a una instruccin SQL "where":


miconsulta = (db.mitabla.micampo != None) | (db.mitabla.micampo > 'A')

Set es un objeto que representa un conjunto de registros. Sus mtodos ms importantes


son count , select , update y delete . Por ejemplo:
miset = db(miconsulta)
registros = miset.select()

miset.update(micampo='unvalor')
miset.delete()

Expression es por ejemplo una expresin orderby o groupby . La clase Field se deriva de
Expression. He aqu un ejemplo.
miorden = db.mitabla.micampo.upper() | db.mitabla.id
db().select(db.mitabla.ALL, orderby=miorden)

Cadenas de conexin
Las conexiones con la base de datos se establecen creando una instancia del objeto DAL:
>>> db = DAL('sqlite://storage.db', pool_size=0)
db no es una palabra especial; es una variable local que almacena el objeto de la

conexin DAL . Puedes usar otro nombre si es necesario. El constructor de DAL requiere un
nico argumento, la cadena de conexin o connection string. La cadena de conexin es el
nico cdigo de web2py que es especfico del motor de la base de datos utilizado. Aqu se
muestran algunos ejemplos de cadenas de conexin para tipos especficos de bases de datos
soportadas (en todos los casos, se asume que la base de datos se est corriendo en localhost
con el puerto por defecto y que se llama "prueba"):
SQLite

sqlite://storage.db

MySQL

mysql://usuario:contrasea@localhost/prueba

PostgreSQL

postgres://usuario:contrasea@localhost/prueba

MSSQL

mssql://usuario:contrasea@localhost/prueba

FireBird

firebird://usuario:contrasea@localhost/prueba

Oracle

oracle://usuario/contrasea@prueba

DB2

db2://usuario:contrasea@prueba

Ingres

ingres://usuario:contrasea@localhost/prueba

Sybase

sybase://usuario:contrasea@localhost/prueba

Informix

informix://usuario:contrasea@prueba

Teradata

teradata://DSN=dsn;UID=usuario;PWD=contrasea;DATABASE=prueba

Cubrid

cubrid://usuario:contrasea@localhost/prueba

SAPDB

sapdb://usuario:contrasea@localhost/prueba

IMAP

imap://usuario:contrasea@server:port

MongoDB

mongodb://usuario:contrasea@localhost/prueba

Google/SQL

google:sql

Google/NoSQL

google:datastore

Ten en cuenta que en SQLite la base de datos consiste de un nico archivo. Si este no existe,
se crear. El archivo se bloquea cada vez que se accede a l. En el caso de MySQL,
PostgreSQL, MSSQL, FireBird, Oracle, DB2, Ingres e Informix la base de datos "prueba" se
debe crear fuera de web2py. Una vez que se ha establecido la conexin, web2py crear,
alterar y eliminar las tablas segn sea necesario.
Adems es posible establecer la cadena de conexin a None . En este caso DAL no conectar
a ningn servicio de base de datos, aunque se podr acceder a la API para pruebas. Para ver
ejemplos de este caso particular consulta el Captulo 7.
A veces puedes necesitar generar un comando SQL como si tuvieras una conexin a una
base de datos pero sin una conexin real. Esto se puede hacer con
db = DAL('...', do_connect=False)

En este caso podrs llamar a _select , _insert , _update y _delete para generar el SQL sin
llamar

a select , insert , update o delete .

En

la

mayora

de

los

casos

puedes

usar do_connect=False incluso sin la presencia de los controladores de la base de datos.


Observa que por defecto web2py usa la codificacin de caracteres utf8. Si trabajas con bases
de datos que tienen otro comportamiento, debes cambiar el parmetro opcional db_code , por
ejemplo, con este comando
db = DAL('...', db_codec='latin1')

de lo contrario obtendrs ticket de error UnicodeDecodeError.

Agrupamiento de conexiones
El segundo argumento del constructor de DAL es el pool_size ; este valor es cero por defecto.
Como el proceso de abrir una nueva conexin a la base de datos para cada solicitud es
bastante lento, web2py implementa un mecanismo para agrupamiento de conexiones
o connection pooling. Una vez que se ha establecido una conexin y una pgina se ha servido
y se ha completado la transaccin, en lugar de cerrar la conexin, esta es guardada en un
cach o pool. Al arribar una nueva solicitud, web2py intenta reciclar la conexin con la
informacin del cach. y utilizarla para la nueva transaccin. Si no existen conexiones
disponibles en el cach, se establece una nueva conexin.
Al iniciarse web2py, el cach de conexiones est siempre vaco. El cach crece hasta un valor
mnimo entre el valor de pool_size y el mximo de solicitudes simultneas. Esto significa que
si se verifica pool_size=10 pero tu servidor no puede recibir ms de 5 consultas simultneas,
entonces el valor real del cach de conexiones crecer solamente hasta 5. Si pool_size=0 ,
entonces el cach de conexiones no se utilizar.
Las conexiones del cach se comparten en forma secuencial entre distintos hilos, en el
sentido de que pueden ser utilizados por dos hilos consecutivos pero no simultneos. Solo hay
un cach de conexiones para cada proceso de web2py.
El parmetro pool_size es ignorado para SQLite y Google App Engine.
Para el caso de SQLite, el cach de conexin se omite ya que no implica ningn beneficio.

Fallas de conexin
Si web2py no puede conectar a la base de datos, espera 1 segundo y lo vuelve a intentar
hasta 5 veces antes de devolver una falla. En el caso del cach de conexiones es posible que

una conexin del cach que contina abierta pero sin uso por algn tiempo sea cerrada del
lado de la base de datos. Gracias a la funcionalidad para restablecimiento de la conexin
web2py intenta recuperar estas conexiones cerradas.

Rplicas de la base de datos


El primer argumento de DAL(...) puede ser una lista compuesta por distintos URI. En este
caso web2py trata de conectar a cada una de ellas. El objeto principal para esto es el manejo
de mltiples servicios de bases de datos y la distribucin de la carga de tareas entre ellos.
Este es un caso de uso tpico:
db = DAL(['mysql://...1','mysql://...2','mysql://...3'])

En este caso la DAL intenta conectar a la primera y, si falla, intentar conectar a la segunda y
a la tercera. Esto se puede usar tambin para distribuir la carga en una configuracin masterslave. Daremos ms detalles sobre esto en el captulo 13 cuando tratemos sobre
escalabilidad.

Palabras reservadas
check_reserved es otro argumento adicional que se puede pasar al constructor de DAL. Le

indica que debe comprobar que los nombres de tablas y columnas no coincidan con palabras
reservadas de SQL para el motor de bases de datos utilizado.
El argumento check_reserved es por defecto None.
check_reserved es una lista de cadenas que contienen los nombres de los motores de base
de datos.
El nombre del adaptador es el mismo usado en la cadena de conexin de DAL. Por lo tanto, si
quisieras comprobar que no existan conflictos de nombres reservados para PostgreSQL y
MSSQL, los argumentos del constructor de DAL deberan ser:
db = DAL('sqlite://storage.db',
check_reserved=['postgres', 'mssql'])

La DAL examinar las palabras especiales en el mismo orden que el especificado en la lista.
Hay dos opciones adicionales "all" y "common". Si especificas all, se comprobarn todas las
palabras reservadas SQL. Si especificas common, solo verificar las palabras comunes
como SELECT , INSERT , UPDATE , etc.

Para un motor de base de datos determinado, puedes adems especificar si quieres que se
comprueben las palabras especiales pero no reservadas de SQL. En ese caso debes
agregar _nonreserved al nombre. Por ejemplo:
check_reserved=['postgres', 'postgres_nonreserved']

Los siguientes motores de base de datos contemplan la verificacin de palabras especiales.


PostgreSQL

postgres(_nonreserved)

MySQL

mysql

FireBird

firebird(_nonreserved)

MSSQL

mssql

Oracle

oracle

DAL

, Table, Field

Puedes experimentar con la API de DAL usando la consola de web2py.


Comienza creando una conexin. Para seguir estos ejemplos, puedes usar SQLite. Nada de lo
que se trata aqu cambia cuando modificas el motor de la base de datos.
>>> db = DAL('sqlite://storage.db')

La conexin con la base de datos se ha establecido y se almacen en la variable global db .


En cualquier momento puedes recuperar la cadena de conexin.
>>> print db._uri
sqlite://storage.db

y el nombre de la base de datos


>>> print db._dbname
sqlite

La cadena de conexin tiene asignado el nombre _uri porque es una instancia de un


identificador uniforme de recursos (Uniform Resource Identifier).
La DAL permite mltiples conexiones con la misma base de datos o a distintas bases de
datos, incluso para distintos tipos de bases de datos. Por ahora, suponemos la presencia de
una nica conexin de base de datos ya que es lo normal en la mayora de las situaciones.
El mtodo ms importante de DAL es define_table :
>>> db.define_table('persona', Field('nombre'))

Esto define, almacena y devuelve un objeto Table llamado "persona" que contiene un campo
(columna) "nombre". Este objeto puede tambin recuperarse a travs de db.persona , por lo
que no necesitas obligatoriamente el valor devuelto por el mtodo.
No debes declarar un campo denominado "id", porque web2py lo crear automticamente.
Toda tabla tiene un campo "id" por defecto. Se trata de un campo de tipo entero de incremento
automtico (auto-increment) que toma como primer valor el 1, usado para referencias entre
tablas (cross-reference) y para que cada registro sea nico, de tal manera que "id" sea una
clave primaria. (Nota: que sea 1 el valor inicial depende del motor de la base de datos. Por
ejemplo, esta propiedad no se aplica para Google App Engine en su versin NoSQL.)
Como opcin, puedes definir un campo especificando type='id' y web2py lo usar como
campo id de incremento automtico. Esto no se recomienda, a menos que se trate del acceso
a tablas de una base de datos heredada o preexistente. Con ciertas limitaciones, tambin es
posible usar claves primarias distintas y esto se tratar en la seccin "Bases de datos
heredadas y tablas con claves".
Las tablas se pueden definir solo una vez pero puedes hacer que web2py redefina una tabla
existente:
db.define_table('persona', Field('nombre'))
db.define_table('persona', Field('nombre'), redefine=True)

La redefinicin puede generar una migracin si el contenido del campo es distinto.

Como usualmente en web2py los modelos se ejecutan antes que los controladores, es
posible que algunas tablas se definan incluso cuando no se las necesita. Por eso, para
acelerar la ejecucin del cdigo es necesario que las definiciones de tablas sean
perezosas (lazy). Esto se hace especificando el atributo DAL(..., lazy_tables=True) . Las
tablas se crearn nicamente cuando se acceda a ellas.

Representacin de registros
Si bien es opcional, se recomienda especificar el formato de representacin para los registros:
>>> db.define_table('persona', Field('nombre'), format='%(nombre)s')

o
>>> db.define_table('persona', Field('nombre'), format='%(nombre)s %(id)s')

o incluso formas ms complicadas usando una funcin:


>>> db.define_table('persona', Field('nombre'),
format=lambda r: r.name or 'annimo')

El atributo de formato se usar con dos fines:


o

Para representar registros asociados (reference) en los mens desplegables.

Para establecer un atributo db.otratabla.persona.represent para todos los campos que


refieran a esa tabla. Esto quiere decir que SQLTABLE no mostrar las referencias como
id sino que usar el formato establecido en su lugar.

Estos son los valores por defecto del constructor Field:


Field(nombre, 'string', length=None, default=None,
required=False, requires='<default>',
ondelete='CASCADE', notnull=False, unique=False,
uploadfield=True, widget=None, label=None, comment=None,
writable=True, readable=True, update=None, authorize=None,
autodelete=False, represent=None, compute=None,
uploadfolder=os.path.join(request.folder,'uploads'),
uploadseparate=None,uploadfs=None)

No todos estos atributos son aplicables a cada tipo de campo. "length" es relevante slo para
campos de tipo "string". "uploadfield" y "authorize" son relevantes nicamente para campos de
tipo "upload". "ondelete" es relevante slo para campos de tipo "reference" y "upload".

length establece la longitud mxima de un campo "string", "password" o "upload".

Si length no se especifica, se usa un valor por defecto pero no se garantiza que este
valor por defecto sea compatible hacia atrs. Para evitar las migraciones innecesarias al
hacer un upgrade, es recomendable especificar siempre la longitud de los campos string,
password y upload.
default establece el valor por defecto del campo. Este valor se usa cuando se realiza

una insercin y el valor para ese campo no se especific en forma explcita. Tambin se
utiliza para precompletar los formularios creados con SQLFORM. Ten en cuenta que,
adems de tomar un valor fijo, el valor por defecto tambin puede ser una funcin
(incluyendo las funciones lambda) que devuelve un valor del tipo correspondiente para el
campo. En este caso, la funcin es llamada una vez por cada registro insertado, incluso
si se estn insertando mltiples registros en una sola transaccin.
required le dice a DAL que no se debera realizar una insercin en la tabla si no se ha

especificado un valor en forma explcita para ese campo.


requires es un validador o una lista de validadores. Esto no es utilizado por DAL, pero

s es usado por SQLFORM. Los validadores por defecto para cada tipo de campo se
listan en la siguiente tabla:
tipo de campo

validadores de campo por defecto

string

IS_LENGTH(length) la longitud por defecto es 512

text

IS_LENGTH(65536)

blob

None

boolean

None

integer

IS_INT_IN_RANGE(-1e100, 1e100)

double

IS_FLOAT_IN_RANGE(-1e100, 1e100)

decimal(n,m)

IS_DECIMAL_IN_RANGE(-1e100, 1e100)

date

IS_DATE()

time

IS_TIME()

datetime

IS_DATETIME()

password

None

upload

None

reference <table>

IS_IN_DB(db, tabla.campo, format)

list:string

None

list:integer

None

list:reference <table>

IS_IN_DB(db, tabla.campo, format, multiple=True)

json

IS_JSON()

bigint

None

big-id

None

big-reference

None

Decimal requiere y devuelve valores como objetos Decimal , segn lo especificado en el


mdulo decimal de Python. SQLite no maneja el tipo decimal por lo que internamente se lo
maneja como un valor double . Los (n, m) son las cantidades de dgitos en total y el nmero
de dgitos despus de la coma respectivamente.
El big-id y, big-reference estn nicamente soportados por algunos motores de bases de
datos y son experimentales. No se usan normalmente como tipo de campo a menos que lo
requieran bases de datos heredadas. Sin embargo, el constructor de DAL tiene un
argumento bigint_id que
si
se
establece
como True convierte
los
campos id y reference en big-id y big-reference respectivamente.

Los tipos de campo list:<tipo> son especiales porque estn diseados para aprovechar
algunas funcionalidades de desnormalizacin en NoSQL (para el caso de Google App Engine
NoSQL, los tipos de campo ListProperty y StringListProperty ) y para que tambin sean
compatibles con servicios de bases de datos relacionales. En las bases de datos relacionales
las listas se almacenan como campos tipo text . Los tems se separan por | y cada | en una
cadena de texto se escapa como || . Estos campos se detallan en una seccin especial.
El campo de tipo json se explica por si mismo. Puede almacenar todo objeto serializable
como JSON. Est diseado para funcionar especficamente con MongoDB y provee de
compatibilidad y portabilidad para los dems motores de bases de datos soportados.

Observa que requires=... se controla en el nivel de los fomularios, required=True se


controla en el nivel de DAL (insercin), mientras que notnull , unique y ondelete se
controlan en el nivel de la base de datos. Si bien a veces esto puede parecer redundante,
es importante observar esta distincin cuando se programa con DAL.
o

ondelete se traduce en una instruccin "ON DELETE" de SQL. Por defecto se

establece como "CASCADE". Esto le dice a la base de datos que si se elimina un


registro, tambin debera eliminar los registros asociados a l. Para deshabilitar esta
funcionalidad, establece ondelete como "NO ACTION" o "SET NULL".
o

notnull=True se traduce en una instruccin "NOT NULL" de SQL. Esto evita que la

base de datos inserte valores nulos para el campo.


o

unique=True se traduce en una instruccin "UNIQUE" de SQL y se asegura de que los

valores del campo sean nicos para la tabla. Esto se controla en el nivel de la base de
datos.
o

uploadfield se aplica nicamente a campos de tipo "upload". Un campo de tipo

"upload" almacena el nombre de un archivo almacenado en otra ubicacin, por defecto


en el sistema de archivos dentro de la carpeta "uploads/" de la aplicacin.
Si uploadfield se especifica, entonces el archivo es almacenado en un campo blob de la
tabla y el valor de uploadfield debe ser el nombre del campo blob. Esto se tratar con
ms detalle en la seccin de SQLFORM.
o

uploadfolder es por defecto la carpeta "uploads/" de la aplicacin. Si se cambia a otra

ruta, los archivos subidos se almacenarn en una carpeta distinta. Por ejemplo,
Field(..., uploadfolder=os.path.join(request.folder, 'static/temp'))

subir los archivos a la carpeta "web2py/applications/miapp/static/temp"

uploadseparate si se especifica True subir los archivos en distintas subcarpetas

de uploadfolder. Esto permite optimizar el almacenamiento de archivos para evitando que


se guarden muchos archivos en un mismo directorio. ADVERTENCIA: no puedes cambiar
el valor de uploadseparate de True a False sin romper los enlaces a los directorios
actuales. web2py puede o bien usar carpetas separadas o no hacerlo. El cambio del
comportamiento una vez que se han subido archivos har que web2py no pueda
recuperarlos. En tal caso, es posible mover los archivos reparando el problema, pero
descripcin del procedimiento no se detalla en esta seccin.
o

uploadfs te permite especificar un sistema de archivos diferente para la subida de

archivos, incluyendo un sistema de almacenamiento S3 de Amazon o un servidor SFTP


remoto. Esta opcin requiere tener instalado PyFileSystem. uploadfs debe estar
enlazado a PyFileSystem . uploadfs
o

widget debe

ser uno de los objetos widget disponibles, incluyendo widget

personalizados, por ejemplo: SQLFORM.widgets.string.widget . En otra seccin se


presenta una lista descriptiva de los widget predeterminados. Cada tipo de campo tiene
un widget por defecto.
o

label es una cadena (o un ayudante o cualquier objeto que admita la serializacin

como cadena) que contiene la etiqueta que se usar para este campo en los formularios
autogenerados.
o

comment es una cadena (o un ayudante u otro objeto serializable como cadena) que

contenga un comentario asociado con este campo, y se mostrar a la derecha del campo
de ingreso de datos en los formularios autogenerados.
o

writable indica si un campo se puede editar en los formularios.

readable indica si un campo se puede visualizar los formularios. Si no se puede

escribir ni leer un campo, entonces no se mostrar en los formularios para crear o


modificar.
o

update contiene el valor por defecto para este campo cuando se actualice el registro.

compute es una funcin opcional. Si el registro se inserta o actualiza, la funcin de

compute se ejecuta y el campo se completar con el valor devuelto por la funcin. El


registro se pasa como argumento de la funcin compute como un objeto dict , y el dict no
incluir el valor del campo a procesar ni el valor de ningn otro campo con el parmetro
compute.

authorize se puede usar para requerir control de acceso para ese campo, solo para

campos "upload". Esto se tratar con ms detalle cuando hablemos de Control de


Acceso.
o

autodelete determina si el archivo subido que corresponde al campo tambin se

debera eliminar cuando se elimine el registro que lo contiene. Este parmetro se


establece nicamente para el tipo de campo "upload".
o

represent puede ser None o una funcin; la funcin recibe el valor actual del campo

como argumento y devuelve una representacin alternativa de ese valor. Ejemplos:


db.mitabla.nombre.represent = lambda nombre, registro: nombre.capitalize()
db.mitabla.otro_id.represent = lambda id, registro: registro.micampo
db.mitabla.un_uploadfield.represent = lambda valor, registro: \
A('Descrgalo haciendo clic aqu', _href=URL('download', args=valor))

Los campos "blob" tambin son especiales. Por defecto, la informacin binaria se codifica
como base64 antes de ser almacenada en el campo de la base de datos en s, y es
decodificado cuando se extrae. Esto tiene como desventaja un incremento del 25% del uso de
la capacidad de almacenamiento para los campos blob, pero adems implica dos beneficios.
En promedio reduce la cantidad de informacin que se transmite entre web2py y el servidor de
la base de datos, y hace que la transferencia de datos sea independiente del sistema utilizado
para el escapado de caracteres especiales.
La mayor parte de los atributos de los campos y tablas se pueden modificar despus de la
definicin:
db.define_table('persona', Field('nombre',default=''),format='%(nombre)s')
db.persona._format = '%(nombre)s/%(id)s'
db.persona.nombre.default = 'annimo'

(observa que los atributos de las tablas normalmente llevan un subguin como prefijo para
evitar posibles conflictos con los nombres de los campos).
Puedes listar las tablas que se han definido para una determinada conexin de la base de
datos:
>>> print db.tables
['persona']

Adems puedes listar los campos que se hayan definido para una tabla determinada:
>>> print db.persona.fields
['id', 'nombre']

Puedes consultar el tipo de una tabla:


>>> print type(db.persona)
<class 'gluon.sql.Table'>

y puedes recuperar una tabla a travs de una conexin de la base de datos usando:
>>> print type(db['persona'])
<class 'gluon.sql.Table'>

En forma similar, puedes acceder a los campos especificando el nombre, de distintas formas
equivalentes:
>>> print type(db.persona.nombre)
<class 'gluon.sql.Field'>
>>> print type(db.person['nombre'])
<class 'gluon.sql.Field'>
>>> print type(db['persona']['nombre'])
<class 'gluon.sql.Field'>

Dado un campo, puedes recuperar sus atributos establecidos en la definicin:


>>> print db.persona.nombre.type
string
>>> print db.persona.nombre.unique
False
>>> print db.persona.nombre.notnull
False
>>> print db.persona.nombre.length
32

incluyendo la tabla a la que pertenecen, el nombre de la tabla y la conexin de la base de


datos de referencia:
>>> db.persona.nombre._table == db.persona
True
>>> db.persona.nombre._tablename == 'persona'
True
>>> db.persona.nombre._db == db
True

Un campo tiene adems mtodos. Algunos de ellos se usan para crear consultas y los
veremos ms adelante. validate es un mtodo especial del objeto campo, que llama a los
validadores para ese campo.
print db.persona.nombre.validate('Juan')

que devuelve una tupla (valor, error) . error es None si el valor de entrada pasa la
validacin.

Migraciones
define_table comprueba la existencia de la tabla definida. Si la tabla no existe, genera el SQL

para crearla y lo ejecuta. Si la tabla existe pero se detectan cambios en la definicin, crea el
SQL para modificar (alter) la tabla y lo ejecuta. si un campo cambi su tipo pero no su nombre,
intentar realizar una conversin de los datos (si no quieres que realice la conversin, debes
redefinir la conversin dos veces, la primera vez, dejando que web2py descarte el campo
eliminndolo, y la segunda vez agregando el nuevo campo definido para que web2py lo pueda
crear.). Si la tabla existe y coincide con la definicin actual, lo dejar intacto. En cualquier caso
crear el objeto db.person que representa la tabla.
Nos referiremos a este comportamiento por el trmino "migracin" o migration. web2py registra
todos los intentos de migraciones en un archivo de log "databases/sql.log".
El primer argumento de define_table es siempre un nombre de tabla. Los otros argumentos
sin nombre, es decir, de tipo positional, son los campos (Field). La funcin tambin acepta un
argumento opcional llamado "migrate", que se debe especificar explcitamente por nombre,
con la siguiente notacin:
>>> db.define_table('persona', Field('nombre'), migrate='persona.tabla')

El valor de migrate es el nombre del archivo (en la carpeta "databases" de la aplicacin) en el


cual web2py almacena la informacin interna de la migracin para esa tabla.
Estos archivos son muy importantes y no se deberan eliminar mientras las existan las tablas
correspondientes. En casos en los que una tabla se ha descartado y su archivo
correspondiente exista, se puede eliminar en forma manual. Por defecto, migrate se establece
como True. Esto hace que web2py genere un nombre de archivo a partir de un hash de la
cadena de conexin. Si migrate se establece como False, no se realiza la migracin, y web2py
asume que la tabla existe en la base de datos y que contiene (por lo menos) los campos
listados en define_table . Lo ms aconsejable es asignar un nombre especfico a la tabla de
migrate.
No deberan existir dos tablas en la misma aplicacin con el mismo nombre de archivo de
migracin.
La clase DAL tambin acepta un argumento "migrate", que determina el valor por defecto de
migrate para cada llamada a define_table . Por ejemplo,
>>> db = DAL('sqlite://storage.db', migrate=False)

establecer por defecto el valor de migrate como False cuando se llame a db.define_table sin
argumentos.

Observa que web2py slo realiza la migracin de nuevas columnas, columnas eliminadas
y cambios de tipo de columna (excepto para sqlite). web2py no realiza la migracin de
cambios en atributos como por ejemplo los valores
de default , unique , notnull y ondelete .
Se pueden deshabilitar las migraciones para todas las tablas con un solo comando:
db = DAL(..., migrate_enabled=False)

Este es el comportamiento recomendado cuando dos app comparten la misma base de datos.
Slo una de las dos app debera realizar las migraciones, en la otra app deberan estar
deshabilitadas.

Reparacin de migraciones fallidas


Hay dos problemas comunes respecto de las migraciones y hay formas de recuperar el normal
funcionamiento.

Uno de los problemas es especfico de SQLite. SQLite no realiza un control de los tipos de
columnas y no las puede descartar. Esto implica que si tienes un tipo de columna string y lo
eliminas, no se eliminar en la prctica. Si agregas el campo nuevamente con un tipo distinto
(por ejemplo datetime) obtendrs un campo datetime que contenga cadenas ilegales (es decir,
informacin intil para el campo). web2py no devuelve un error en este caso porque no sabe lo
que contiene la base de datos, pero s lo har cuando intente recuperar los registros.
Si web2py devuelve un error en la funcin gluon.sql.parse cuando recupera registros, este es
el problema: informacin corrupta en una columna debido al caso descripto ms arriba.
La solucin consiste en actualizar todos los registros de la tabla y actualizar los valores en la
columna en cuestin como None.
El otro problema es ms genrico pero tpico de MySQL. MySQL no permite ms de un
ALTER TABLE en una transaccin. Esto quiere decir que web2py debe separar las
transacciones complicadas en operaciones ms pequeas (con un ALTER TABLE por vez) y
realizar un commit por operacin. Por lo tanto, es posible que parte de una transaccin
compleja se aplique y que otra genere un error, dejando a web2py inestable. Por qu puede
fallar una parte de una transaccin? Porque, por ejemplo, implica la modificacin de la tabla y
la conversin de una columna de texto en una datetime; web2py intentar convertir la
informacin, pero la informacin no se puede modificar. Qu ocurre en web2py? Se produce
un conflicto porque no es posible determinar la estructura de la tabla actualmente almacenada
en la base de datos.
La solucin consiste en deshabilitar las migraciones para todas las tablas y habilitar las
migraciones ficticias o fake migration:
db.define_table(...., migrate=True, fake_migrate=True)

Esto reconstruir los metadatos de web2py sobre la tabla segn su definicin. Prueba
mltiples definiciones de tablas para ver cul de ellas funciona (la definida antes de la
migracin fallida y la definida luego de la falla de migracin). Cuando logres recuperar las
migraciones, elimina el atributo fake_migrate=True .
Antes de intentar arreglar problemas de migracin es conveniente hacer una copia de los
archivos de "applications/tuapp/databases/*.table".
Los problemas relacionados con la migracin tambin se pueden solucionar para todas las
tablas en un solo paso:
db = DAL(..., fake_migrate_all=True)

Esto tambin fallar si el modelo describe tablas que no existen en la base de datos, pero
puede ser til como solucin parcial.

insert

Dada una tabla determinada, puedes insertar registros


>>> db.persona.insert(nombre="Alejandro")
1
>>> db.persona.insert(nombre="Roberto")
2

Insert devuelve un valor nico "id" para cada registro insertado.


Puedes truncar la tabla, es decir, borrar todos sus registros y reiniciar el contador de los
valores id.
>>> db.persona.truncate()

Ahora, si insertas un registro nuevamente, el contador iniciar con un valor de 1 (esto depende
del motor de la base de datos y no se aplica en Google NoSQL):
>>> db.persona.insert(nombre="Alejandro")
1

Observa que puedes pasar parmetros a truncate , por ejemplo puedes especificar que
SQLITE debe reiniciar el contador.
db.persona.truncate('RESTART IDENTITY CASCADE')

El argumento es SQL puro y por lo tanto especfico de cada motor.


web2py tambin cuenta con un mtodo para inserciones mltiples bulk_insert
>>> db.persona.bulk_insert([{'nombre':'Alejandro'}, {'nombre':'Juan'}, {'nombre':'Timoteo'}])
[3,4,5]

Acepta una lista de diccionarios de campos que se insertarn y realiza mltiples inserciones
en un solo comando. Devuelve los IDs de los registros insertados. Para las bases de datos
relacionales soportadas, no implica un beneficio el uso de esta funcin en lugar de generar

cada insercin en un bucle, pero en Google App Engine NoSQL, significa un aumento
considerable de la velocidad.

commit

y rollback

En realidad, no se aplica ninguna operacin para crear, descartar, truncar o modificar hasta
que se agregue el comando commit
>>> db.commit()

Para comprobarlo insertemos un nuevo registro:


>>> db.persona.insert(nombre="Roberto")
2

y devolvmosle su estado anterior (rollback), es decir, ignoremos toda operacin desde el


ltimo commit:
>>> db.rollback()

Si hacemos ahora un insert, el contador se establecer nuevamente como 2, ya que las


inserciones previas se han anulado.
>>> db.persona.insert(nombre="Roberto")
2

El cdigo en los modelos, vistas y controladores est envuelto en cdigo de web2py similar a
lo siguiente:
try:
ejecutar el modelo, la funcin del controlador y la vista
except:
reestablecer (rollback) todas las conexiones
registrar la traza del error
enviar un ticket al visitante
else:
aplicar los cambios en todas las conexiones (commit)
guardar los cookie, la sesin y devolver la pgina

No es necesario llamar a commit o rollback en forma explcita en web2py a menos que uno
requiera un control ms pormenorizado.

SQL puro
Cronometrado de las consultas
Todas las consultas son cronometradas automticamente por web2py. La
variable db._timings es una lista de tuplas. Cada tupla contiene la consulta en SQL puro tal
como se pasa al controlador y el tiempo que tom su ejecucin en segundos. Esta variable se
puede mostrar en las vistas usando la barra de herramientas o toolbar:
{{=response.toolbar()}}

executesql

La DAL te permite ingresar comandos SQL en forma explcita.


>>> print db.executesql('SELECT * FROM persona;')
[(1, u'Massimo'), (2, u'Massimo')]

En el ejemplo, los valores devueltos no son analizados o convertidos por la DAL, y el formato
depende del controlador especfico de la base de datos. Esta forma de utilizacin por medio
de comandos select no se requiere normalmente, aunque es ms comn para los ndices.
executesql acepta cuatro argumentos opcionales: placeholders , as_dict , fields y colnames .
placeholders es una secuencia opcional de valores a sustituir, o, si el controlador de la base

de
datos
lo
contempla,
un
diccionario
variables placeholder especificadas en el SQL.

con

claves

que

coinciden

con

Si se establece as_dict como True, el cursor de los resultados devuelto por el controlador se
convertir en una secuencia de diccionarios cuyos nombres de clave corresponden a los
nombres de los campos. Los registros devueltos con as_dict=True iguales que los devueltos
cuando se usa .as_list() con un select normal.
[{campo1: valor1, campo2: valor2}, {campo1: valor1b, campo2: valor2b}]

El argumento fields es una lista de campos DAL que coinciden con los campos devueltos por
la base de datos. Los objetos Field deberan componer uno o ms objetos Table definidos en
el objeto DAL. La lista de fields puede incluir ono o ms objetos Table de DAL, como

agregado o en lugar de incluir objetos Field, o puede ser una sola tabla (no una lista de
tablas). Para el ltimo caso, los objetos Field se extraern de la(s) o tabla(s).
Tambin

es

posible

especificar

tanto fields y

los colnames asociados.

En

este

caso, fields puede tambin incluir objetos Expression de DAL adems de los objetos Field.
Para los objetos Field en "fields", los nombres de columna asociados de colnames pueden
tomar nombres arbitrarios.
Ten
en
cuenta
que
los
objetos
Table
de
DAL
referidos
en
los
parmetros fields y colnames pueden ser tablas ficticias y no tienen que ser obligatoriamente
tablas reales de la base de datos. Adems, los valores para fields y colnames deben tener el
mismo orden que los campos en el cursor de los resultados devueltos por la base de datos.
_lastsql

Tanto para el caso de SQL ejecutado manualmente usando executesql como para SQL
generado por la DAL, siempre puedes recuperar el cdigo SQL en db._lastsql . Esto es til
para la depuracin:
>>> registros = db().select(db.persona.ALL)
>>> print db._lastsql
SELECT persona.id, persona.nombre FROM persona;

web2py nunca genera consultas usando el operador "*". Las selecciones de campos son
siempre explcitas.

drop

Por ltimo, puedes descartar (drop) una tabla, y por lo tanto, toda su informacin almacenada:
>>> db.persona.drop()

ndices
Actualmente la API de la DAL no provee de un comando para crear ndices de tablas, pero eso
se puede hacer utilizando el comando executesql . Esto se debe a que la existencia de ndices
puede hacer que las migraciones se tornen complejas, y es preferible que se especifiquen en
forma explcita. Los ndices o index pueden ser necesarios para aquellos campos que se
utilicen en consultas recurrentes.

Este es un ejemplo para la creacin de un ndice usando SQL con SQLite:


>>> db = DAL('sqlite://storage.db')
>>> db.define_table('persona', Field('nombre'))
>>> db.executesql('CREATE INDEX IF NOT EXISTS miidx ON persona (nombre);')

Otros dialectos de bases de datos usan una sintaxis muy similar pero pueden no soportar la
instruccin "IF NOT EXISTS".

Bases de datos heredadas y tablas con claves


web2py puede conectar con bases de datos heredadas o legadas bajo ciertas condiciones.
La forma ms fcil es cuando se cumplen estas condiciones:
o

Cada tabla debe tener un campo auto incrementado de valores enteros llamado "id"

Los registros deben tener referencias a otros registros exclusivamente por medio del
campo "id".

Cuando se accede a una tabla existente, por ejemplo, una tabla que no se ha creado con
web2py en la aplicacin actual, siempre se debe especificar migrate=False .
Si la tabla heredada tiene un campo auto incrementado de valores enteros pero no se llama
"id", web2py tambin puede leerlo pero la definicin de la tabla debe contener
explcitamente Field('...', 'id') donde ... es el nombre del campo auto incrementado de valores
enteros.
Por ltimo, si la tabla heredada usa una clave primaria que no es un campo id auto
incrementado es posible usar una tabla con claves okeyed table, por ejemplo:
db.define_table('cuenta',
Field('numero','integer'),
Field('tipo'),
Field('descripcion'),
primarykey=['numero','tipo'],
migrate=False)

primarykey es una lista de nombres de campo que componen la clave primaria.

Todos los campos de clave primaria tienen campos NOT NULL incluso cuando no se
especifica el atributo.

Las tablas con claves solo pueden tener referencias a otras tablas con claves.

Los

campos

de

referencia

deben

usar

el

formato reference

nombredetabla.nombredecampo .

La funcin update_record no est disponible para objetos Row de tablas con claves.

Actualmente las tablas con claves estn soportadas para DB2, MS-SQL, Ingres e Informix,
pero se agregar soporte para otros motores.
Al tiempo de esta edicin, no podemos garantizar que el atributo primarykey funcione para
toda tabla heredada existente en todas las bases de datos soportadas.
Para mayor simplicidad se recomienda, si es posible, crear una vista de la base de datos que
incorpore un campo id auto incrementado.

Transacciones distribuidas
Al tiempo de esta edicin, esta caracterstica est soportada nicamente por PostgreSQL,
MySQL y Firebird, ya que estos motores exponen una API para aplicar modificaciones en
dos fases (two phase commit).
Suponiendo que tienes dos (o ms) conexiones a distintos servicios de base de datos
PostgreSQL, por ejemplo:
db_a = DAL('postgres://...')
db_b = DAL('postgres://...')

En tus modelos o controladores, puedes aplicar cambios en forma simultnea con:


DAL.distributed_transaction_commit(db_a, db_b)

Si se produce un error, esta funcin recupera el estado anterior y genera una Exception .
En los controladores, cuando finaliza una accin, si tienes dos conexiones distintas y no usas
la funcin descripta arriba, web2py aplica los cambios en forma separada. Esto implica que
existe la posibilidad de que uno de los cambios se aplique satisfactoriamente y otro no. La
transaccin distribuida evita que esto ocurra.

Ms sobre las subidas de archivos


Consideremos el siguiente modelo:
>>> db.define_table('miarchivo',
Field('imagen', 'upload', default='ruta/'))

Para el caso de un campo 'upload', el valor por defecto puede ser opcionalmente una ruta (una
ruta absoluta o relativa a el directorio de la app actual) y la imagen por defecto se configurar
como una copia del archivo especificado en la ruta. Se hace una nueva copia por cada nuevo
registro para el que no se especifique una imagen.
Normalmente una insercin se maneja automticamente por medio de un SQLFORM o un
formulario crud (que de hecho es un SQLFORM) pero a veces ya dispones del archivo en el
sistema y quieres subirlo en forma programtica. Esto se puede hacer de la siguiente forma:
>>> stream = open(nombredelarchivo, 'rb')
>>> db.miarchivo.insert(imagen=db.miarchivo.imagen.store(stream, nombredearchivo))

Tambin es posible insertar un archivo en una forma ms simple y hacer que el mtodo de
insercin llame a store automticamente:
>>> stream = open(nombredearchivo, 'rb')
>>> db.miarchivo.insert(imagen=stream)

Esta vez el nombre del archivo se obtiene del objeto stream si se hubiera especificado.
El mtodo store del objeto del campo upload acepta un stream de un archivo y un nombre de
archivo. Usa el nombre del archivo para determinar la extensin (tipo) del archivo, crea un
nuevo nombre temporario para el archivo (segn el mecanismo para subir archivos de
web2py) y carga el contenido del archivo en este nuevo archivo temporario (dentro de la
carpeta uploads a menos que se especifique otra ubicacin). Devuelve el nuevo nombre
temporario, que es entonces almacenado en el campo imagen de la tabla db.miarchivo .
Ten en cuenta que si el archivo se debe almacenar en un campo blob asociado en lugar de
usar el sistema de archivos, el mtodo store() no insertar el archivo en el campo blob
(porque store() es llamado despus de insert), por lo que el archivo se debe insertar
explcitamente en le campo blob:
>>> db.define_table('miarchivo',

Field('imagen', 'upload', uploadfield='archivo'),


Field('archivo', 'blob'))
>>> stream = open(nombredearchivo, 'rb')
>>> db.miarchivo.insert(imagen=db.miarchivo.imagen.store(stream, nombredearchivo),
archivo=stream.read())
.retrieve es lo opuesto a .store :
>>> registro = db(db.miarchivo).select().first()
>>> (nombredearchivo, stream) = db.miarchivo.imagen.retrieve(registro.imagen)
>>> import shutil
>>> shutil.copyfileobj(stream, open(nombredearchivo,'wb'))

Query

, Set, Rows

Ahora consideremos la tabla definida (y descartada) previamente e insertemos tres registros:


>>> db.define_table('persona', Field('nombre'))
>>> db.persona.insert(name="Alejandro")
1
>>> db.persona.insert(name="Roberto")
2
>>> db.persona.insert(name="Carlos")
3

Puedes almacenar la tabla en una variable. Por ejemplo, con la variable persona , puedes
hacer:
>>> persona = db.persona

Adems puedes almacenar un campo en una variable como nombre . Por ejemplo, tambin
puedes hacer:
>>> nombre = persona.nombre

Incluso puedes crear una consulta (usando operadores como ==, !=, <, >, <=, >=, like,
belongs) y almacenar la consulta en una variable q como en:
>>> q = nombre=='Alejandro'

Cuando llamas a db con una consulta, puedes definir un conjunto de registros. Puedes
almacenarlos en una variable s y escribir:
>>> s = db(q)

Ten en cuenta que hasta aqu no se ha realizado una consulta a la base de datos en s. DAL +
Query simplemente definen un conjunto de registros en esta base de datos que coinciden con
los parmetros de la consulta. web2py determina a partir de la consulta cul tabla (o tablas) se
incluyeron y, de hecho, no hay necesidad de especificarlas.

select

Dado un conjunto Set, s , puedes recuperar sus registros con el comando select :
>>> registros = s.select()

Esto devolver un objeto iterable de la clase gluon.sql.Rows cuyos elementos son objetos
Row. Los objetos gluon.sql.Row funcionan de la misma forma que un diccionario, pero sus
elementos tambin se pueden acceder como atributos, como con gluon.storage.Storage La
diferencia de Row con Storage es que los atributos de Row son de solo lectura.
El objeto Rows permite recorrer el resultado de un comando select y recuperar los valores de
los distintos campos de cada registro:
>>> for registro in registros:
print registro.id, registro.nombre
1 Alejandro

Puedes unir todos los pasos en un comando:


>>> for registro in db(db.persona.nombre=='Alejandro').select():
print registro.nombre
Alejandro

El comando select puede recibir parmetros. Todos los argumentos posicionales se interpretan
como los nombres de los campos que quieres recuperar. Por ejemplo, puedes recuperar
explcitamente los campos "id" y "nombre":
>>> for registro in db().select(db.persona.id, db.persona.nombre):
print registro.nombre
Alejandro
Roberto
Carlos

El atributo de tabla ALL te permite especificar todos los campos:


>>> for registro in db().select(db.persona.ALL):
print registro.nombre
Alejandro
Roberto
Carlos

Observa que no hay cadena de consulta pasada a la base de datos. web2py interpreta que si
quieres todos los campos de la tabla persona sin otra informacin, entonces quieres todos los
registros de la tabla persona.
La siguiente es una sintaxis alternativa equivalente:
>>> for registro in db(db.persona.id > 0).select():
print registro.nombre
Alejandor
Roberto
Carlos

y web2py entiende que si preguntas por todos los registros de una tabla persona (id > 0) sin
informacin adicional, entonces quieres todos los campos de la tabla persona.
Dado un registro
registro = registos[0]

puedes extraer sus valores usando mltiples expresiones equivalentes:


>>> registro.nombre
Alejandro
>>> row['nombre']
Alejandro
>>> row('persona.nombre')
Alejandro

La primer sintaxis usada es particularmente til cuando se usan expresiones en lugar de


columnas en select. Daremos ms detalles sobre esto ms adelante.
Adems puedes hacer
registros.compact = False

para deshabilitar la notacin


registro[i].nombre

y habilitar, en cambio, la notacin menos compacta:


registro[i].persona.nombre

Esto no es usual y raramente necesario.

Atajos
La DAL soporta varios atajos para simplificar el cdigo fuente.
En especial:
miregistro = db.mitabla[id]

devuelve el registro con el id dado en caso de que exista. Si el id no existe, entonces


devuelve None . La instruccin es equivalente a
miregistro = db(db.mitabla.id==id).select().first()

Puedes eliminar registros por id:

del db.mitabla[id]

y esto es equivalente a
db(db.mitabla.id==id).delete()

y elimina los registros con el id dado, si es que existe.


Puedes insertar registros:
db.mitabla[0] = dict(micampo='unvalor')

es el equivalente de
db.mitabla.insert(micampo='unvalor')

y crea un nuevo registro con los valores especificados en el diccionario a la derecha de cada
igualdad.
Puedes modificar registros:
db.mitabla[id] = dict(micampo='unvalor')

que es equivalente a
db(db.mitabla.id==id).update(micampo='unvalor')

y actualizar un registro existente con los valores de campo especificados por el diccionario a
la derecha de la igualdad.

Recuperando un registro Row


Esta otra sintaxis tambin es recomendable:
registro = db.mitabla(id)
registro = db.mitabla(db.mitabla.id==id)
registro = db.mitabla(id, micampo='unvalor')

Parece similar a db.mitabla[id] , aunque esta ltima sintaxis es ms flexible y segura. En


primer lugar comprueba si id es un entero (o str(id) es un entero) y devuelve None si no lo
es (y nunca genera una excepcin). Adems permite especificar mltiples condiciones que el
registro debe cumplir. Si estas condiciones no se cumplen, entonces devolver None .

select

recursivos

Consideremos la tabla anterior de personas y una nueva tabla "cosa" que hace referencia a
"persona":
>>> db.define_table('cosa',
Field('nombre'),
Field('id_propietario','reference persona'))

y un simple select de esa tabla:


>>> cosas = db(db.cosa).select()

que es equivalente a
>>> cosas = db(db.cosa._id>0).select()

donde ._id es una referencia a la clave primaria de la tabla. Normalmente db.cosa._id es lo


mismo que db.cosa.id y lo tomaremos como convencin en la mayor parte del libro.
Por cada Row de cosas es posible recuperar no solamente campos de la tabla seleccionada
(cosa) sino tambin de las tablas enlazadas (en forma recursiva):
>>> for cosa in cosas: print cosa.nombre, cosa.id_propietario.nombre

Aqu cosa.id_propietario.nombre requiere un select de la base de datos para cada cosa en


cosas y por lo tanto no es eficiente. Se sugiere el uso de instrucciones join cuando sea posible
en lugar de select recursivos, aunque esto s es conveniente cuando se accede a registros
individuales.
Tambin es posible la operacin en sentido opuesto, es decir, un recuperar las cosas que
tienen a una persona como referencia:
persona = db.persona(id)
for cosa in persona.cosa.select(orderby=db.cosa.nombre):
print persona.nombre, 'es dueo de', cosa.nombre

En esta ltima expresin persona.cosa es un atajo para


db(db.cosa.id_propietario==person.id)

es decir, el conjunto o Set de los cosa que tienen a la persona actual como referencia. Esta
sintaxis se daa si la tabla que hace la referencia tiene mltiples referencias a la otra tabla. En
ese caso uno debe ser ms explcito y usar una consulta con la notacin completa.

Serializacin de registros Rows en las vistas


Dada la accin siguiente conteniendo una consulta
def index()
return dict(registros=db(consulta).select())

El resultado de un select se puede mostrar en una vista con la siguiente sintaxis:


{{extend 'layout.html'}}
<h1>Registros</h1>
{{=registros}}

Que es equivalente a:
{{extend 'layout.html'}}
<h1>Registros</h1>
{{=SQLTABLE(registros)}}
SQLTABLE convierte los registros en una tabla HTML con un encabezado que contiene los

nombres de columna y una fila de tabla por registro de la base de datos. Los registros se
marcan en forma alternada como clase "even" y "odd" (par e impar). En forma transparente
para el desarrollador, los registros del objeto Rows son primero convertidos en un objeto
SQLTABLE (no se debe confundir con la clase Table) y luego son serializados. Los valores que
se extraen de la base de datos tambin reciben un formato por medio de los validadores
asociados a cada campo y luego se escapan.
De todos modos, es posible y a veces conveniente el uso de SQLTABLE en forma explcita.
El constructor de SQLTABLE toma los siguientes parmetros opcionales:
o

linkto es el URL o accin a usar para enlazar los campos reference (None por defecto)

upload el URL o accin de descarga para permitir la descarga o subida de archivos

(None por defecto)

headers un diccionario que asocia nombres de campo a las etiquetas que se usarn

como encabezados (por defecto es {} ). Tambin puede ser una instruccin.


Actualmente se contempla headers='nombredecampo:capitalize' .
o

truncate el nmero de caracteres para el truncado de valores extensos en la tabla (por

defecto es 16)
o

columns es la lista de nombresdecampo a mostrarse como columnas (en el formato

nombredetabla.nombredecampo). Aquellos que no se listen no se mostrarn (por defecto


muestra todos los campos).
o

**attributes son atributos comunes de ayudante html que se pasarn al objeto TABLE

ms externo.
He aqu un ejemplo:
{{extend 'layout.html'}}
<h1>Registros</h1>
{{=SQLTABLE(registros,
headers='nombredecampo:capitalize',
truncate=100,
upload=URL('download'))
}}
SQLTABLE

es muy til pero a veces uno puede necesitar algo ms

avanzado. SQLFORM.grid es una extensin de SQLTABLE que crea una tabla con
herramientas de bsqueda y paginacin, as como tambin la habilidad de visualizar
detalles, crear, y borrar registros. SQLFORM.smartgrid tiene un nivel mayor de abstraccin
que permite todas las caractersticas anteriores pero adems crea botones para el acceso
a los registros de referencia.
Este es un ejemplo de uso de SQLFORM.grid :
def index():
return dict(grid=SQLFORM.grid(consulta))

y la vista correspondiente:
{{extend 'layout.html'}}

{{=grid}}
SQLFORM.grid y SQLFORM.smartgrid se deberan preferir a SQLTABLE porque son ms

potentes aunque de mayor nivel y por lo tanto con ms restricciones. Estos ayudantes se
tratan con mayor detalle en el captulo 7
orderby, groupby, limitby, distinct, having

El comando select toma cinco argumentos opcionales: orderby, groupby, limitby, left y cache.
Aqu trataremos sobre los primeros tres.
Puedes recuperar los registros ordenados por nombre:
>>> for registro in db().select(
db.persona.ALL, orderby=db.persona.nombre):
print registro.nombre
Alejandro
Roberto
Carlos

Puedes recuperar los registros ordenados por nombre en el orden inverso (observa el uso del
tilde):
>>> for registro in db().select(
db.persona.ALL, orderby=~db.persona.nombre):
print registro.nombre
Carlos
Roberto
Alejandro

Puedes hacer que los registros recuperados aparezcan en orden inverso:


>>> for registro in db().select(
db.persona.ALL, orderby='<random>'):
print registro.nombre
Carlos

Alejandro
Roberto

El uso de orderby='<random>' no est soportado para Google NoSQL. Sin embargo, en


esta situacin as como tambin en muchas otras donde las caractersticas incorporadas
no son suficientes, se pueden importar otras:
import random
rows=db(...).select().sort(lambda row: random.random())

Puedes ordenar los registros segn mltiples campos unindolos con un "|":
>>> for registro in db().select(
db.persona.ALL, orderby=db.persona.nombre|db.persona.id):
print registro.nombre
Carlos
Roberto
Alejandro

Usando groupby junto con orderby , puedes agrupar registros con el mismo valor para un
campo determinado (esto depende del motor de la base de datos, y no est soportado para
Google NoSQL):
>>> for registro in db().select(
db.persona.ALL,
orderby=db.persona.nombre, groupby=db.persona.nombre):
print registro.nombre
Alejandro
Roberto
Carlos

Puedes usar having en conjunto con groupby para agrupar en forma condicional (se
agruparn solo aquellos que cumplan la condicin).
>>> print db(consulta1).select(db.persona.ALL, groupby=db.persona.nombre,
having=consulta2)

Observa que consulta1 filtra los registros a mostrarse, consulta2 filtra los registros que se
agruparn.
Con los argumentos distinct=True , puedes especificar que solo quieres recuperar registros
nicos (no repetidos). Esto tiene el mismo resultado que agrupar los registros usando todos
los campos especificados, con la excepcin de que no requiere ordenarlos. Cuando se usa
distinct es importante que no se recuperen todos los campos con ALL, y en especial que no se
use el campo "id", de lo contrario todo registro ser nico.
Aqu se puede ver un ejemplo:
>>> for registro in db().select(db.persona.nombre, distinct=True):
print registro.nombre
Alejandro
Roberto
Carlos

Observa que distinct tambin puede ser una expresin como:


>>> for registro in db().select(db.persona.nombre, distinct=db.persona.nombre):
print registro.nombre
Alejandro
Roberto
Carlos

Con limitby=(mnimo, mximo), puedes recuperar un subconjunto de registros a partir de


desplazamiento=mnimo hasta y sin incluir desplazamiento=mximo (para este caso, los
primeros dos comenzando desde cero):
>>> for registro in db().select(db.persona.ALL, limitby=(0, 2)):
print registro.nombre
Alejandro
Roberto

Operadores lgicos
Las consultas se pueden combinar con el operador binario " & ":

>>> registros = db((db.persona.nombre=='Alejandro') & (db.persona.id>3)).select()


>>> for registro in rows: print registro.id, registro.nombre
4 Alejandro

y el operador binario OR " | ":


>>> registros = db((db.persona.nombre=='Alejandro') | (db.persona.id>3)).select()
>>> for registro in rows: print registro.id, registro.nombre
1 Alejandro

Puedes negar una consulta (o subconsulta) con el operador binario " != ":
>>> registros = db((db.persona.nombre!='Alejandro') | (db.persona.id>3)).select()
>>> for registro in rows: print registro.id, registro.nombre
2 Roberto
3 Carlos

o por negacin explcita con el operador unitario " ~ ":


>>> registros = db(~(db.persona.nombre=='Alejandro') | (db.persona.id>3)).select()
>>> for registro in rows: print registro.id, registro.nombre
2 Roberto
3 Carlos

Debido a restricciones de Python con respecto a la sobrecarga de los operadores " and " y
" or ", estos operadores no se pueden usar para crear consultas; deben usarse en cambio
los operadores binarios " & " y " | ". Ten en cuenta que estos operadores (a diferencia de
" and " y " or ") tienen una precedencia mayor que los operadores de comparacin, por lo
que los parntesis adicionales en los ejemplos de arriba son obligatorios. En forma similar,
el operador unitario " ~ " tiene una precedencia mayor a la de los operadores de
comparacin, y por lo tanto, las comparaciones negadas con ~ tambin se deben
encerrar entre parntesis.
Adems es posible la creacin de consultas usando operadores lgicos compuestos (inplace):
>>> consulta = db.persona.nombre!='Alejandro'

>>> consulta &= db.persona.id>3


>>> consulta |= db.persona.nombre=='Juan'

count, isempty, delete, update

Puedes contar los registros de un conjunto Set:


>>> print db(db.persona.id > 0).count()
3

Observa que count toma un argumento opcional distinct que por defecto es False, y
funciona en forma bastante parecida al argumento para el comando select . count adems
tiene un argumento cache que funciona en forma similar a su equivalente para el
mtodo select .
En ocasiones puedes necesitar comprobar si una tabla est vaca. Una forma ms eficiente de
contar registros es usando el mtodo isempty :
>>> print db(db.persona.id > 0).isempty()
False

o su equivalente:
>>> print db(db.persona).isempty()
False

Puedes eliminar registros de un conjunto Set:


>>> db(db.persona.id > 3).delete()

Y puedes modificar todos los registros de un conjunto Set pasando argumentos de par
nombre-valor que correspondan a los campos que se deben modificar:
>>> db(db.persona.id > 3).update(nombre='Ken')

Expresiones
El valor asignado para un comando de modificacin update puede ser una expresin. Por
ejemplo, consideremos este modelo
>>> db.define_table('persona',

Field('nombre'),
Field('visitas', 'integer', default=0))
>>> db(db.persona.nombre == 'Mximo').update(
visitas = db.persona.visitas + 1)

Los valores usados en las consultas tambin pueden ser expresiones


>>> db.define_table('persona',
Field('nombre'),
Field('visitas', 'integer', default=0),
Field('clic', 'integer', default=0))
>>> db(db.persona.visits == db.persona.clic + 1).delete()

case

Una expresin puede contener una instruccin case como por ejemplo en:
>>> db.define_table('persona', Field('nombre'))
>>> condicion = db.persona.nombre.startswith('M')
>>> si_o_no = condition.case('Yes','No')
>>> for registro in db().select(db.persona.nombre, si_o_no):
...

print registro.persona.nombre, registro(si_o_no)

Mximo Yes
Juan No

update_record

otra caracterstica de web2py es que permite actualizar un registro nico que ya se encuentre
en memoria utilizando update_record
>>> registro = db(db.persona.id==2).select().first()
>>> registro.update_record(noombre='Curt')

no se debe confundir update_record con


>>> registro.update(nombre='Curt')

porque para un nico registro, el mtodo update modificar el objeto Row del registro pero no
el registro en s en la base de datos, como ocurre para el caso de update_record .
Tambin es posible cambiar los atributos de un registro Row (uno por vez), y entonces llamar
a update_record() sin argumentos para actualizar los cambios:
>>> registro = db(db.persona.id > 2).select().first()
>>> registro.nombre = 'Curt'
>>> registro.update_record() # guarda los cambios de arriba

El mtodo update_record est disponible slo cuando se incluye el campo id en el comando


select y no se ha habilitado la opcin cacheable .

Insercin y modificacin por medio de diccionarios


Un problema usual es el de la necesidad de insertar o modificar registros de una tabla cuando
el nombre de la tabla, el campo a modificar y el valor del campo se han almacenado en
variables. Por ejemplo: nombredetabla , nombredecampo y valor .
La insercin se puede hacer usando la siguiente sintaxis:
db[nombredetabla].insert(**{nombredecampo:valor})

La actualizacin del registro para un id dado se puede hacer con:


db(db[nombredetabla]._id==id).update(**{nombredecampo:valor})

Observa que hemos usado tabla._id en lugar de tabla.id . De esta forma la consulta funciona
incluso para tablas con un campo de tipo "id" que tiene un nombre distinto de "id".
first

y last

Dado un objeto Rows que contiene registros:


>>> registros = db(consulta).select()
>>> primero = registros.first()
>>> ultimo = registros.last()

son equivalentes a

>>> primero = registros[0] if len(registros)>0 else None


>>> ultimo = registros[-1] if len(registros)>0 else None

as_dict

y as_list

Un objeto Row se puede serializar como un diccionario normal usando el mtodo as_dict() y
un objeto Rows se puede serializar como una lista de diccionarios usando el mtodo as_list() .
Aqu se muestran algunos ejemplos:
>>> registros = db(consulta).select()
>>> lista = registros.as_list()
>>> primer_diccionario = registros.first().as_dict()

Estos mtodos son convenientes para pasar objetos Rows a las vistas genricas y para
almacenar objetos Rows en sesiones (ya que los objetos Rows en s no se pueden serializar
por contener una referencia a una conexin abierta de la base de datos):
>>> registros = db(consulta).select()
>>> sesion.registros = registros # prohibido!
>>> sesion.registros = registros.as_list() # permitido!

Combinando registros
Los objetos Row se pueden combinar con mtodos de Python. Aqu se asume que:
>>> print registros1
persona.nombre
Mximo
Timoteo
>>> print registros2
persona.nombre
Juan
Timoteo

Puedes realizar una unin de registros a partir de dos conjuntos de registros:


>>> registros3 = registros1 & registros2

>>> print registros3


nombre
Mximo
Timoteo
Juan
Timoteo

Tambin puedes hacer una unin de registros eliminando los duplicados:


>>> registros3 = registros1 | registros2
>>> print registros3
nombre
Mximo
Timoteo
Juan

find, exclude, sort

A veces necesitas realizar dos select y uno contiene un subconjunto del otro. Para este caso,
no tiene sentido acceder nuevamente a la base de datos. Los objetos find , exclude y sort te
permiten manipular un objeto Rows generando una copia sin acceder a la base de datos.
Especficamente:
o

find devuelve un conjunto Rows filtrado por una condicin determinada sin modificar el

original.
o

exclude devuelve un conjunto Rows filtrado por una condicin y los elimina del Rows

orginal.
o

sort devuelve un conjunto Rows ordenado por una condicin y no realiza cambios en

el original.
Estos mtodos toman un nico argumento, una funcin que realiza una operacin para cada
registro.
Este es un ejemplo de uso:
>>> db.define_table('persona', Field('nombre'))

>>> db.persona.insert(name='Juan')
>>> db.persona.insert(name='Max')
>>> db.persona.insert(name='Alejandro')
>>> registros = db(db.persona).select()
>>> for registro in registros.find(lambda registro: registro.nombre[0]=='M'):
print registro.nombre
Mximo
>>> print len(registros)
3
>>> for registro in registros.exclude(lambda registro: registro.nombre[0]=='M'):
print registro.nombre
Mximo
>>> print len(registro)
2
>>> for registro in registros.sort(lambda registro: registro.nombre):
print registro.nombre
Alejandro
Juan

Tambin se puede combinar mtodos:


>>> registros = db(db.persona).select()
>>> registros = registros.find(
lambda registro: 'x' in registro.nombre).sort(
lambda registro: registro.nombre)
>>> for registro in registros:
print registro.nombre
Alejandro
Max

Sort toma un argumento opcional reverse=True cuyo significado es obvio.

El mtodo find tiene un argumento opcional limitby con la misma sintaxis y caractersticas
que su anlogo para el mtodo select del objeto Set.

Otros mtodos
update_or_insert

A veces puedes necesitar realizar una insercin slo si no hay registros con el mismo valor
que los que se estn insertando. Esto puede hacerse con
db.define_table('persona', Field('nombre'), Field('lugardenacimiento'))
db.persona.update_or_insert(name='Juan', birthplace='Chicago')

El registro se insertar slo si no existe otro usuario llamado Juan que haya nacido en
Chicago.
Puedes especificar qu valores se usarn como criterio para determinar si el registro existe.
Por ejemplo:
db.persona.update_or_insert(db.persona.nombre=='Juan',
name='Juan', lugardenacimiento='Chicago')

y si existe el tal Juan, su lugardenacimiento se actualizar o de lo contrario se crear un nuevo


registro.
validate_and_insert, validate_and_update

La funcin
resultado = db.mitabla.validate_and_insert(campo='valor')

es prcticamente lo mismo que


id = db.mitabla.insert(campo='valor')

con la excepcin de que la primera llama a los validadores para los campos antes de realizar
las inserciones y no aplica los cambios si no se cumplen los requisitos. Si la validacin
fracasa, los errores se pueden recuperar de resultado.error . Si tiene xito, se puede recuperar
el id del nuevo registro con resultado.id . Recuerda que normalmente la validacin se hace a
travs de los algoritmos para el procesamiento de formularios, por lo que est funcin se debe
usar en situaciones especiales.

En forma similar
resultado = db(consulta).validate_and_update(campo='valor')

es prcticamente lo mismo que


numero = db(consulta).update(campo='valor')

salvo que el primer comando llama a los validadores para los campos antes de realizar la
modificacin. Observa que adems funcionar nicamente si la consulta est restringida a una
sola tabla. El nmero de registros actualizados se puede encontrar en resultado.updated y los
errores se almacenarn en resultado.errors .
smart_query

(experimental)

Hay veces que uno necesita analizar una consulta usando lenguaje natural como por ejemplo:
nombre contains m and edad greater than 18

La DAL provee de un mtodo para analizar este tipo de consultas:


busqueda = 'nombre contain m and edad greater than 18'
registros = db.smart_query([db.persona], busqueda).select()

El primer argumento debe ser una lista de tablas o campos que se deberan admitir en una
bsqueda. Si la cadena de la consulta es invlida, se generar una excepcin RuntiemError .
Esta funcionalidad se puede usar para crear interfaces RESTful (ver captulo 10) y es usada
internamente por SQLFORM.grid y SQLFORM.smartgrid .
En la cadena de bsqueda smartquery, un campo se puede declarar tanto con la sintaxis
nombredecampo como con la notacin nombredetabla.nombredecampo. En caso de contener
espacios, las cadenas deberan delimitarse por comillas dobles.

Campos calculados
Los campos de DAL tienen un atributo compute . Este atributo debe ser una funcin (o
lambda) que recibe un objeto Row y devuelve un nuevo valor para el campo. Cuando se
modifica un nuevo registro, tanto para las inserciones como para las modificaciones, si el valor
para el campo no se provee, web2py intentar calcularlo a partir de otros valores de campos
usando la funcin de compute . Aqu se muestra un ejemplo:

>>> db.define_table('item',
Field('precio_unitario','double'),
Field('cantidad','integer'),
Field('precio_total',
compute=lambda r: r['precio_unitario']*r['cantidad']))
>>> r = db.item.insert(precio_unitario=1.99, cantidad=5)
>>> print r.precio_total
9.95

Observa que el valor calculado se almacena en la base de datos y no se calcula al


recuperarse, como ocurre en el caso de los campos virtuales, que se detallan ms adelante.
Hay dos aplicaciones tpicas para los campos calculados:
o

en aplicaciones wiki, para almacenar el texto de entrada procesado como HTML


evitando el reprocesamiento en cada solicitud

cuando se quiere calcular valores normalizados para un campo determinado,


optimizando las bsquedas

Campos virtuales
Los campos virtuales son tambin campos calculados (como los detallados en la seccin
anterior) pero difieren de estos en que sonvirtuales en el sentido de que no se almacenan en
la base de datos y se calculan cada vez que los registros se extraen con una consulta. Este
tipo de campos es de utilidad cuando se quiere simplificar el cdigo del usuario sin agregar
espacio de almacenamiento, pero no es posible usarlo en bsquedas.

Nuevo estilo para campos virtuales


web2py provee de una nueva forma, ms fcil, para la definicin de campos virtuales y
campos virtuales perezosos (lazy virtual fields). Esta seccin est marcada como experimental
porque la API de esta nueva caracterstica puede sufrir ligeras modificaciones respecto de lo
que se detalla a continuacin.
Aqu vamos a considerar el mismo ejemplo que en la seccin previa. Particularmente,
suponemos el siguiente modelo:
>>> db.define_table('item',

Field('precio_unitario', 'double'),
Field('cantidad', 'integer'))

Se puede definir un campo precio_total virtual de esta forma


>>> db.item.precio_total = Field.Virtual(
lambda registro: registro.item.precio_unitario*registro.item.cantidad)

es decir, basta con definir un nuevo campo precio_total de tipo Field.Virtual . El nico
argumento del constructor es una funcin que recibe un registro y devuelve el resultado del
clculo.
Un campo virtual definido como se muestra arriba se calcula automticamente para todo
registro que sea resultado de un comando select:
>>> for registro in db(db.item).select(): print registro.precio_total

Tambin se puede definir campos de tipo especial mtodo que se calculan nicamente por
encargo, cuando se los llama. Por ejemplo:
>>> db.item.total_descuento = Field.Method(lambda registro, descuento=0.0: \
registro.item.precio_unitario*registro.item.cantidad*(1.0-descuento/100))

En este caso registro.total_descuento no es un valor sino una funcin. La funcin toma el


mismo argumento que la funcin pasada al constructor de Method , con la diferencia de
que registro es implcito (se puede comparar con el self de los objetos Row).
El campo perezoso del ejemplo de arriba nos permite calcular el precio total para cada item :
>>> for registro in db(db.item).select(): print registro.total_descuento()

Y adems, permite pasar un porcentaje opcional del descuento (15%):


>>> for registro in db(db.item).select(): print registro.total_descuento(15)

Los campos tipo Virtual y Method tambin se pueden definir al definir la tabla:
>>> db.define_table('item',
Field('precio_unitario','double'),
Field('cantidad','integer'),

Field.Virtual('precio_total', lambda registro: ...),


Field.Method('total_descuento', lambda registro, descuento=0.0: ...))

Ten en cuenta que los campos virtuales no tienen los mismos atributos que los demas
campos (default, readable, requires, etc) y que no se listan en el conjunto almacenado
en db.tabla.fields , y que no se visualizan por defecto en las tablas (TABLE) y grid
(SQLFORM.grid, SQLFORM.smartgrid).

El viejo estilo de campos virtuales.


Para poder definir uno o ms campos virtuales, puedes adems definir una clase contenedora,
instanciarla y asociarla con una tabla o un select. Por ejemplo, consideremos la siguiente
tabla:
>>> db.define_table('item',
Field('precio_unitario','double'),
Field('cantidad','integer'),

Uno puede definir un campo virtual precio_total de esta forma


>>> class MisCamposVirtuales(object):
def precio_total(self):
return self.item.precio_unitario*self.item.cantidad
>>> db.item.virtualfields.append(MisCamposVirtuales())

Observa que cada mtodo de la clase que toma un nico argumento (self) es un nuevo campo
virtual. self hace referencia a cada objeot Row del comando select. Los valores de campos se
recuperan por su ruta completa self.item.precio_unitario . La tabla se asocia a los campos
virtuales agregando una instancia de la clase al atributo virtualfields de la tabla.
Los campos virtuales tambin se pueden acceder en forma recursiva a otros campos, como en
el siguiente ejemplo
>>> db.define_table('item',
Field('precio_unitario','double'))
>>> db.define_table('item_compra',
Field('item','reference item'),
Field('cantidad','integer'))

>>> class MisCamposVirtuales(object):


def precio_total(self):
return self.item_compra.item.precio_unitario \
* self.item_compra.cantidad
>>> db.item_compra.virtualfields.append(MisCamposVirtuales())

Observa el acceso al campo en forma recursiva con self.item_compra.precio_unitario ,


donde self es cada elemento Row recuperado de la consulta.
Tambin pueden operar en funcin del resultado de un JOIN
>>> db.define_table('item',
Field('precio_unitario','double'))
>>> db.define_table('item_compra',
Field('item','reference item'),
Field('cantidad','integer'))
>>> registros = db(db.item_compra.item==db.item.id).select()
>>> class MisCamposVirtuales(object):
def precio_total(self):
return self.item.precio_unitario \
* self.item_compra.cantidad
>>> registros.setvirtualfields(item_compra=MisCamposVirtuales())
>>> for registro in registros: print registro.item_compra.precio_total

Observa como para este caso la sintaxis es distinta. El campo virtual accede tanto
a self.item.precio_unitario como a self.item_compra.cantidad , que pertenece al select con join.
El campo virtual se adjunta a los registros Row de la tabla por medio de el
mtodo setvirtualfields del objeto Rows (registros). Este mtodo toma un nmero arbitrario de
argumentos de par nombre-valor y se puede usar para definir mltiples campos virtuales,
mltiples clases, y adjuntarlos a mltiples tablas.
>>> class MisCamposVirtuales1(object):
def precio_rebajado(self):
return self.item.precio_unitariio*0.90

>>> class MisCamposVirtuales2(object):


def precio_total(self):
return self.item.unit_price \
* self.item_compra.cantidad
def precio_total_rebajado(self):
return self.item.precio_rebajado \
* self.item_compra.cantidad
>>> registros.setvirtualfields(
item=MisCamposVirtuales1(),
item_compra=MisCamposVirtuales2())
>>> for registro in registros:
print registro.item_compra.precio_total_rebajado

Los campos virtuales pueden ser perezosos; todo lo que debes hacer es devolver una funcin
y llamar a esa funcin para recuperarlos:
>>> db.define_table('item',
Field('precio_unitario','double'),
Field('cantidad','integer'),
>>> class MisCamposVirtuales(object):
def precio_total_perezoso(self):
def perezoso(self=self):
return self.item.precio_unitario \
* self.item.cantidad
return perezoso
>>> db.item.virtualfields.append(MisCamposVirtuales())
>>> for item in db(db.item).select():
print item.precio_total_perezoso()

o ms corto usando una funcin lambda:


>>> class MisCamposVirtuales(object):

def precio_total_perezoso(self):
return lambda self=self: self.item.precio_unitario \
* self.item.cantidad

Relacin uno a muchos


Para ilustrar cmo se debe implementar una relacin de uno a muchos con la DAL de web2py,
definiremos una tabla "cosa" que est asociada a otra tabla "persona", modificando los
ejemplos anteriores de la siguiente forma:
>>> db.define_table('persona',
Field('nombre'),
format='%(nombre)s')
>>> db.define_table('cosa',
Field('nombre'),
Field('id_propietario', 'reference persona'),
format='%(nombre)s')

La tabla "cosa" tiene dos campos, el nombre de la cosa y el propietario de la cosa. El campo
de los id "id_propietario" es un campo reference. El tipo reference se puede especificar de dos
formas:
Field('id_propietario', 'reference persona')
Field('id_propietario', db.persona)

El segundo ejemplo siempre se convierte a la forma del primero. Son equivalentes a excepcin
del caso de las tablas perezosas, los campos que refieren a la misma tabla o self reference u
otros tipos de referencias circulares donde la primera notacin es la nica admitida.
Cuando un tipo de campo es otra tabla, se espera que el campo est vinculado a la otra tabla
por el valor id. De todas formas, puedes devolver el tipo de valor real y obtendrs:
>>> print db.cosa.id_propietario.type
reference persona

Ahora, inserta tres cosas, dos pertenecientes a Alejandro y una a Roberto:

>>> db.cosa.insert(nombre='Bote', id_propietario=1)


1
>>> db.cosa.insert(nombre='Silla', id_propietario=1)
2
>>> db.cosa.insert(nombre='Zapatos', id_propietario=2)
3

Puedes recuperar los registros como usualmente se hara para cualquier otra tabla:
>>> for registro in db(db.cosa.id_propietario==1).select():
print registro.nombre
Bote
Silla

Como una cosa tiene referencia a una persona, una persona puede tener muchas cosas, por
lo que un registro de la tabla persona ahora obtendr un nuevo atributo cosa, que consta de
un conjunto Set, que define las cosas de esa persona. Esto da la posibilidad de recorrer la
listas de personas y recuperar sus cosas en una forma sencilla:
>>> for persona in db().select(db.persona.ALL):
print persona.nombre
for cosa in persona.cosa.select():
print '

', cosa.nombre

Alejandro
Bote
Silla
Roberto
Zapatos
Carlos

Inner join
Otra forma de obtener resultados similares es el uso de los join, especialmente el comando
INNER JOIN o join interno. web2py realiza operaciones join en forma automtica y
transparente cuando una consulta enlaza dos o ms tablas como en el siguiente ejemplo:

>>> registros = db(db.persona.id==db.cosa.id_propietario).select()


>>> for registro in registros:
print registro.persona.nombre, 'tiene', registro.cosa.nombre
Alejandro tiene Bote
Alejandro tiene Silla
Roberto tiene Zapatos

Observe que web2py hizo un join, por lo que ahora contiene dos registros, uno para cada
tabla, enlazados mutuamente. Como los dos registros podran tener campos de igual nombre,
debes especificar la tabla al extraer el valor del campo de un registro. Esto implica que antes
de que puedas hacer:
registro.nombre

donde se deduce fcilmente que se trata del nombre de una cosa o una persona (segn se
haya especificado en la consulta), cuando obtienes el resultado de un join debes ser ms
explcito y decir:
registro.persona.nombre

o bien:
registro.cosa.nombre

Hay una sintaxis alternativa para los INNER JOIN:


>>> registros = db(db.persona).select(join=db.cosa.on(db.persona.id==db.cosa.id_propietario))
>>> for registro in registros:
print registro.persona.nombre, 'tiene', registro.cosa.nombre
Alejandro tiene Bote
Alejandro tiene Silla
Roberto tiene Zapatos

Mientras la salida es la misma, el SQL generado en los dos casos pueden ser diferente. La
segunda sintaxis elimina las posibles ambigedades cuando la misma tabla es operada con
join dos veces y se utilizan alias como en el siguiente ejemplo:
>>> db.define_table('cosa',

Field('nombre'),
Field('id_propietario1','reference persona'),
Field('id_propietario2','reference persona'))
>>> registros = db(db.persona).select(
join=[db.persona.with_alias('id_propietario1').on(db.persona.id==db.cosa.id_propietario1).
db.persona.with_alias('id_propietario2').on(db.persona.id==db.cosa.id_propietario2)])

El valor de join puede ser una lista de db.tabla.on(...) a juntar (join).

Left outer join


Observa que Carlos no apareci en la lista de arriba porque no tiene cosas. Si te propones
recuperar personas (tengan cosas o no) y sus cosas (si las tuvieran), entonces debes realizar
un LEFT OUTER JOIN (join externo a izquierda). Esto se hace usando el argumento "left" del
comando select. He aqu un ejemplo:
>>> registros=db().select(
db.persona.ALL, db.cosa.ALL,
izquierda=db.cosa.on(db.persona.id==db.cosa.id_propietario))
>>> for registro in rows:
print registro.persona.nombre, 'tiene', registro.cosa.nombre
Alejandro tiene Bote
Alejandro tiene Silla
Roberto tiene Zapatos
Carlos tiene None

donde:
left = db.coda.on(...)

hace el join externo a izquierda. Aqu el argumento de db.cosa.on es el requisito para realizar
el join (el mismo usado arriba para el join interno). En el caso de un join a izquierda, es
necesario especificar en forma explcita los campos que se recuperarn.
Se pueden combinar mltiples join a izquierda si se pasa una lista o tupla de
elementos db.mitabla.on(...) al atributo left .

Agrupando y contando
Cuando se hacen operaciones join, a veces quieres agrupar los registros segn cierto criterio
y contarlos. Por ejemplo, contar el nmero de cosas que tiene cada persona. web2py tambin
contempla este tipo de consultas. Primero, necesitas un operador para conteo. Segundo,
necesitas hacer un join entre la tabla persona y la tabla cosa segn el propietario. Tercero,
debes recuperar todos los registros (personas + cosas), agruparlas en funcin de las
personas, y contarlas, ordenadas en grupos:
>>> conteo = db.persona.id.count()
>>> for registro in db(db.persona.id==db.cosa.id_propietario).select(
db.persona.nombre, conteo, groupby=db.persona.nombre):
print registro.persona.nombre, row[conteo]
Alejandro 2
Roberto 1

Observa que el operador de conteo count (que es un elemento incorporado) es utilizado


como campo. La nica cuestin aqu es cmo devolver la informacin. Es evidente que cada
registro contiene una persona y el conteo, pero el conteo en s no es un campo de una
persona ni una tabla. A dnde se ubica entonces? Se ubicar en el objeto storage que es
representacin del registro con una clave igual a la expresin de la consulta misma. El mtodo
count del objeto Field tiene un argumento opcional distinct . Cuando se especifica
como True indica que slo se deben contar los valores del campo cuando no se repitan.

Muchos a muchos
En los ejemplos anteriores, hemos especificado que una cosa tenga un propietario, pero una
persona poda tener mltiples cosa. Qu pasa si el Bote es propiedad tanto de Alejandro
como de Curt? Esto requiere una relacin muchos-a-muchos, y se establece con una tabla
intermedia que enlaza a una persona con una cosa en una relacin de propiedad o
pertenencia.
Esto se hace de la siguiente forma:
>>> db.define_table('persona',
Field('nombre'))
>>> db.define_table('cosa',

Field('nombre'))
>>> db.define_table('pertenencia',
Field('persona', 'reference persona'),
Field('cosa', 'reference cosa'))

las relaciones de pertenencia previas se pueden reescribir ahora de la siguiente forma:


>>> db.pertenencia.insert(persona=1, cosa=1) # Alejandro tiene Bote
>>> db.pertenencia.insert(persona=1, cosa=2) # Alejandro tiene Silla
>>> db.pertenencia.insert(persona=2, cosa=3) # Roberto tiene Zapatos

Ahora puedes agregar la nueva relacin segn la cual Curt es copropietario del Bote:
>>> db.pertenencia.insert(persona=3, cosa=1) # Curt tambin tiene el Bote

Como ahora tienes una relacin triple entre tablas, conviene definir un nuevo conjunto sobre el
cual realizar las operaciones:
>>> personas_y_cosas = db(
(db.persona.id==db.pertenencia.persona) \
& (db.cosa.id==db.pertenencia.cosa))

Ahora es fcil recuperar todas las personas y sus cosas a partir del nuevo conjunto Set:
>>> for registro in personas_y_cosas.select():
print registro.persona.nombre, registro.cosa.nombre
Alejandro Boet
Alejandro Silla
Roberto Zapatos
Curt Bote

De una forma similar, puedes buscar todas las cosas que pertenezcan a Alejandro:
>>> for registro in personas_y_cosas(db.persona.nombre=='Alejandro').select():
print registro.cosa.nombre
Bote

Silla

y todos los dueos del Bote:


>>> for registro in personas_y_cosas(db.cosa.nombre=='Bote').select():
print registro.persona.nombre
Alejandro
Curt

Una alternativa menos exigente para las relaciones muchos a muchos es la de tagging o
selecciones mltiples. El uso de selecciones mltiples se trata en la seccin dedicada al
validador IS_IN_DB . Las selecciones mltiples funcionan incluso en motores de bases de
datos que no contemplan el uso de operaciones JOIN como Google App Engine NoSQL.

list:<type>

y contains

web2py provee de los siguientes tipos especiales de campo:


list:string
list:integer
list:reference <table>

Estos pueden contener listas de cadenas, enteros o referencias respectivamente.


En Google App Engine NoSQL list:string se traduce en un objeto StringListProperty , los otros
dos se traducen en objetos ListProperty(int) . En las bases de datos relacionales se asocian a
campos de tipo text que contienen una lista de tems separados por | . Por ejemplo [1, 2,
3] se convierte en |1|2|3| .

Para las listas de cadenas los tems se escapan para que todo | en el tem se reemplace
por || . De todas formas, se trata de una representacin interna que es transparente al
usuario.
Puedes usar list:string , por ejemplo, de la siguiente forma:
>>> db.define_table('producto',
Field('nombre'),
Field('colores', 'list:string'))

>>> db.producto.colores.requires=IS_IN_SET(('rojo', 'azul', 'verde'))


>>> db.producto.insert(nombre='Auto de juguete', colores=['rojo','verde'])
>>> productos = db(db.producto.colores.contains('rojo')).select()
>>> for item in productos:
print item.nombre, item.colores
Auto de juguete ['rojo', 'verde']
list:integer funciona de la misma forma pero los tems deben ser enteros.

Como siempre, los requisitos se controlan en el nivel de los formularios, no en el nivel


del insert .

Para los campos list:<type> el operador contains(valor) se traduce como una consulta
compleja que busca listas que contengan valor . El operador contains tambin funciona
con campos normales de tipo string y text y se traducen como LIKE '%valor%' .
Los campos list:reference y el operador contains(valor) son especialmente tiles para
desnormalizar las relaciones many-to-many. Aqu hay un ejemplo:
>>> db.define_table('etiqueta',Field('nombre'),format='%(nombre)s')
>>> db.define_table('producto',
Field('nombre'),
Field('etiquetas','list:reference etiqueta'))
>>> a = db.etiqueta.insert(nombre='rojo')
>>> b = db.etiqueta.insert(nombre='verde')
>>> c = db.etiqueta.insert(nombre='azul')
>>> db.producto.insert(name='Auto de juguete',etiquetas=[a, b, c])
>>> productos = db(db.producto.etiquetas.contains(b)).select()
>>> for item in productos:
print item.nombre, item.etiquetas
Auto de juguete [1, 2, 3]
>>> for item in productos:
print item.nombre, db.producto.etiquetas.represent(item.etiquetas)

Auto de juguete rojo, verde, azul

Observa que los campos list:reference etiqueta recibe una restriccin por defecto
requires = IS_IN_DB(db, 'etiqueta.id', db.etiqueta._format, multiple=True)

que produce un men SELECT/OPTION de valores mltiples en formularios.


Adems, este campo recibe un atributo represent por defecto que genera la lista de
referencias como una lista separada por comas de referencias con su formato
correspondiente. Esto se utiliza en los formularios de lectura y tablas SQLTABLE .

Mientras list:reference tiene un validador por defecto y una representacin por defecto,
esto no ocurre con list:integer y list:string . Por lo tanto, estos requieren un
validador IS_IN_SET o IS_IN_DB si quieres usarlos en formularios.

Otros operadores
web2py tiene otros operadores que proveen de una API para el acceso a operadores
equivalentes en SQL. Definamos otra tabla "log" para registrar eventos de seguridad, su
tiempo de registro y el nivel de severidad, donde la severidad es un entero.
>>> db.define_table('log', Field('evento'),
Field('registrado', 'datetime'),
Field('severidad', 'integer'))

Como ya hemos para otros ejemplos, ingresaremos algunos registros, un evento "escner de
puertos" (port scanner), otro con una "secuencia de comandos en sitios cruzados" (xss
injection) y un "acceso sin autenticacin" (unauthorized login).
Para hacer el ejemplo ms simple, puedes registrar eventos que tengan igual tiempo pero
distinta severidad (1, 2 y 3 respectivamente).
>>> import datetime
>>> ahora = datetime.datetime.now()
>>> print db.log.insert(
evento='escner de puertos', registrado=ahora, severidad=1)
1

>>> print db.log.insert(


evento='secuencia de comandos en sitios cruzados', registrado=ahora, severidad=2)
2
>>> print db.log.insert(
evento='acceso sin autenticacin', registrado=ahora, severidad=3)
3

like, regexp, startswith, contains, upper, lower

Los objetos Field tienen un operador que puedes usar para comparar cadenas:
>>> for registro in db(db.log.evento.like('escner%')).select():
print registro.evento
escner de puertos

Aqu "escner%" especifica una cadena que comienza con "escner". El signo de porcentaje,
"%", es un signo especial o wild-card que quiere decir "toda secuencia de caracteres".
El operador like no es sensible a minsculas, pero es posible hacerlo sensible a minsculas
con
db.mitabla.micampo.like('valor', case_sensitive=True)

web2py adems provee de algunos atajos:


db.mitabla.micampo.startswith('valor')
db.mitabla.micampo.contains('valor')

que son equivalentes respectivamente a


db.mitabla.micampo.like('valor%')
db.mitabla.micampo.like('%valor%')

Observa que contains tiene un significado especial en campos list:<type> ; esto se trat en
una seccin previa.
El mtodo contains tambin acepta una lista de valores y un argumento opcional
booleano all , que busca registros que contengan todos los valores de la lista:

db.mitabla.micampo.contains(['valor1','valor2'], all=True)

o al menos uno de los valores de la lista


db.mitabla.micampo.contains(['valor1','valor2'], all=false)

Adems hay un mtodo regexp que funciona en forma similar al mtodo like , pero permite el
uso de la sintaxis de expresiones regulares para la expresin a comparar. Esto est soportado
nicamente en las bases de datos PostgreSQL y SQLite.
Los mtodos upper y lower nos permiten convertir el valor de un campo a maysculas o
minsculas, y es posible combinarlos con el operador like:
>>> for registro in db(db.log.evento.upper().like('ESCNER%')).select():
print registro.evento
escner de puertos

year, month, day, hour, minutes, seconds

Los tipos de campo date y datetime tienen mtodos day, month y year (da, mes y ao). Los
campos de tipo datetime y time tienen mtodos hour, minutes y seconds. He aqu un ejemplo:
>>> for registro in db(db.log.registrado.year()==2013).select():
print registro.evento
escner de puertos
secuencia de comandos en sitios cruzados
acceso sin autenticacin

belongs

El operador de SQL IN se implementa a travs del mtodo belongs, que devuelve true cuando
el valor del campo pertenece (belongs) al conjunto especificado (una lista o tupla):
>>> for registro in db(db.log.severidad.belongs((1, 2))).select():
print registro.event
escner de puertos
secuencia de comandos en sitios cruzados

La DAL tambin permite usar un comando select anidado como argumento del operador
belongs. El nico detalle a tener en cuenta es que el select anidado tiene que ser un _select ,
no select , y se debe especificar un solo campo explcitamente, que es el que define el
conjunto Set.
>>> dias_problematicos = db(db.log.severidad==3)._select(db.log.registrado)
>>> for registro in db(db.log.registrado.belongs(dias_problematicos)).select():
print registro.evento
escner de puertos
secuencia de comandos en sitios cruzados
acceso sin autenticacin

En aquellos casos donde se requiere un select anidado y el campo de bsqueda es una


referencia, tambin es posible usar una consulta como argumento. Por ejemplo:
db.define_table('persona',Field('nombre'))
db.define_table('cosa', Field('nombre'), Field('id_propietario','reference cosa'))
db(db.cosa.id_propietario.belongs(db.persona.nombre=='Jonatan')).select()

En este caso es obvio que el prximo select slo necesita el campo asociado
con db.cosa.id_propietario , por lo que no hay necesidad de usar una notacin _select ms
explcita.
Tambin es posible usar un select anidado como valor de insercin o actualizacin, pero para
este caso la sintaxis es distinta:
perezoso = db(db.persona.nombre=='Jonatan').nested_select(db.persona.id)
db(db.cosa.id==1).update(id_propietario = perezoso)

En este caso, perezoso es una expresin anidada que calcula el id de la persona "Jonatan".
Las dos lneas componen una nica consulta SQL.
sum, avg, min, max

y len

Previamente, hemos usado el operador count para contar registros. En forma similar, puedes
usar el operador sum para sumar los valores de un campo especfico a partir de un conjunto
de registros. Como en el caso de count, el resultado de una suma se recupera a travs del
objeto store:

>>> suma = db.log.severidad.sum()


>>> print db().select(suma).first()[suma]
6

Adems puedes usar avg , min y max para obtener los valores promedio, mnimo y mximo
respectivamente para los registros seleccionados. Por ejemplo:
>>> maximo = db.log.severidad.max()
>>> print db().select(maximo).first()[maximo]
3
.len() calcula la longitud de un campo de tipo string, text o boolean.

Es posible combinar las expresiones para componer otras expresiones ms complejas. Por
ejemplo, aqu calculamos la suma de la longitud de todas las cadenas del campo severidad en
los eventos del log, incrementadas en una unidad:
>>> suma = (db.log.severidad.len()+1).sum()
>>> print db().select(suma).first()[suma]

Subconjuntos de cadenas
Podemos componer una expresin para que haga referencia a un subconjunto de una cadena
o substring. Por ejemplo, podemos agrupar las cosas cuyos nombres tengan las tres letras
iniciales iguales y recuperar slo uno de cada grupo:
db(db.cosa).select(distinct = db.cosa.name[:3])

Valores por defecto usando coalesce y coalesce_zero


A veces necesitas extraer un valor de una base de datos pero adems necesitas valores por
defecto en caso de que un registro contenga el valor NULL. En SQL, hay una palabra especial
para ese propsito, COALESCE . web2py incorpora un mtodo coalesce equivalente:
>>> db.define_table('usuariodelsistema',Field('nombre'),Field('nombre_completo'))
>>> db.sysuser.insert(nombre='max', nombre_completo='Mxima Potencia')
>>> db.sysuser.insert(nombre='tim', nombre_completo=None)

print
db(db.usuariodelsistema).select(db.usuariodelsistema.nombre_completo.coalesce(db.usuariodels
istema.nombre))
"COALESCE(usuariodelsistema.nombre_completo, usuariodelsistema.nombre)"
Mxima Potencia
tim

En otras ocasiones, necesitas calcular una expresin matemtica pero algunos campos tienen
valores nulos que deberan ser cero.
coalesce_zero nos saca del apuro especificando en la consulta que los valores nulos de la

base de datos deben ser cero:


>>> db.define_table('usuariodelsistema',Field('nombre'),Field('puntos'))
>>> db.sysuser.insert(nombre='max', puntos=10)
>>> db.sysuser.insert(nombre='tim', puntos=None)
>>> print db(db.usuariodelsistema).select(db.usuariodelsistema.puntos.coalesce_zero().sum())
"SUM(COALESCE(usuariodelsistema.puntos, 0))"
10

Generacin de SQL puro


A veces puedes necesitar slo generar el SQL en lugar de ejecutarlo. Esto es fcil de hacer en
web2py porque cada comando que realiza una modificacin en la base de datos tiene un
comando equivalente que no realiza la operacin de E/S, sino que simplemente devuelve la
expresin SQL que se debera ejecutar. Estos comandos tienen los mismos nombres y sintaxis
que los que hacen las modificaciones, pero comienzan con un subguin:
Este es el comando de insercin _insert
>>> print db.persona._insert(nombre='Alejandro')
INSERT INTO persona(nombre) VALUES ('Alejandro');

Este es el comando de conteo _count


>>> print db(db.persona.nombre=='Alejandro')._count()
SELECT count(*) FROM persona WHERE persona.nombre='Alejandro';

Y este es el comando para recuperar registros _select


>>> print db(db.persona.nombre=='Alejandro')._select()
SELECT persona.id, persona.nombre FROM persona WHERE persona.nombre='Alejandro';

Ahora el comando de eliminacin _delete


>>> print db(db.persona.nombre=='Alejandro')._delete()
DELETE FROM persona WHERE persona.nombre='Alejandro';

Y por ltimo, este es el comando para modificacin _update


>>> print db(db.persona.nombre=='Alejandro')._update()
UPDATE persona SET WHERE persona.nombre='Alejandro';

De todas formas, siempre es posible usar db._lastsql para recuperar la ltima expresin
SQL, tanto en caso de haberse ejecutado manualmente, como para el caso de la
generacin automtica con DAL.

Importando y Exportando datos


CSV (un objeto Table por vez)
Cuando se convierten los objetos Row a cadena, estos se serializan automticamente como
CSV:
>>> registros = db(db.persona.id==db.cosa.id_propietario).select()
>>> print registros
persona.id, persona.nombre, cosa.id, cosa.nombre, cosa.id_propietario
1, Alejandro, 1, Bote, 1
1, Alejandro,2 , Silla, 1
2, Roberto, 3, Zapatos, 2

Puedes serializar una nica tabla en CSV y almacenarla en un archivo "prueba.csv":


>>> open('prueba.csv', 'wb').write(str(db(db.persona.id).select()))

Esto es equivalente a

>>> registros = db(db.persona.id).select()


>>> registros.export_to_csv_file(open('prueba.csv', 'wb'))

Puedes recuperar los datos de un archivo CSV con:


>>> db.persona.import_from_csv_file(open('prueba.csv', 'r'))

Cuando se importan datos, web2py busca los nombres de campos en el encabezado del CSV.
En este ejemplo, recupera dos columnas: "persona.id" y "persona.nombre". El prefijo
"persona." se omite, y adems se omite el campo "id". Luego todos los registros se agregan a
la tabla asignndoles nuevos valores id. Estas dos operaciones se pueden realizar por medio
de la interfaz web appadmin.

CSV (de todos los objetos Table)


En web2py, puedes hacer copias y restauraciones de la base de datos completa con dos
comandos:
Para exportar:
>>> db.export_to_csv_file(open('unarchivo.csv', 'wb'))

Para importar:
>>> db.import_from_csv_file(open('unarchivo.csv', 'rb'))

Este mecanismo se puede usar incluso si la base de datos que importa los datos es de distinto
tipo que la base de datos exportada. Los datos son almacenados en "unarchivo.csv" con el
formato CSV, donde cada tabla comienza con una lnea que indica el nombre de la tabla, y
otra lnea contiene los nombres de los campos:
TABLE nombredetabla
campo1, campo2, campo3, ...

La separacin entre tablas es \r\n\r\n . El archivo termina con la siguiente lnea:


END

No se incluyen los archivos subidos al sistema, a menos que se trate de archivos


almacenados en la base de datos. En todo caso, una solucin bastante simple consiste en
crear un zip con la carpeta "uploads" por separado.

Cuando se hace la importacin de datos, el nuevo registro se agregar a la base de datos,


siempre y cuando el registro no est vaco. En general, los nuevos registros importados no
tendrn el mismo valor del campo id que en la base de datos original (exportada), pero
web2py recupera las referencias (los enlaces entre tablas para cada registro) de forma que
estas no se pierdan, incluso en el caso de que cambie el valor del id.
Si una tabla contiene un campo llamado "uuid", entonces se usar ese campo para identificar
duplicados. Adems, si un registro importado tiene el mismo "uuid" que otro existente en la
base de datos, el registro preexistente se actualizar con los nuevos valores.

CSV y sincronizacin con una base de datos remota


Consideremos el siguiente modelo:
db = DAL('sqlite:memory:')
db.define_table('persona',
Field('nombre'),
format='%(nombre)s')
db.define_table('cosa',
Field('id_propietario', 'reference persona'),
Field('nombre'),
format='%(nombre)s')

if not db(db.persona).count():
id = db.persona.insert(nombre="Mximo")
db.cosa.insert(id_propietario=id, nombre="Silla")

Cada registro est identificado con un ID y enlazado a otra tabla tambin por ese mismo ID. Si
tienes dos copias de la misma base de datos en uso en dos instalaciones distintas de web2py,
el ID ser nico slo para una base de datos determinada localmente pero no para distintas
bases de datos. Esto resulta problemtico cuando se quieren juntar registros que provienen de
distintas bases de datos.
Para que un registro mantenga su unicidad en distintas bases de datos, deben:
o

tener un id nico (UUID),

tener un campo event_time (para establecer cul es el ms reciente entre mltiples


copias),
tener como referencia el UUID en lugar del id.

Esto es posible sin modificar web2py. Debemos hacer lo siguiente:


Cambia el modelo anterior por el siguiente:
db.define_table('persona',
Field('uuid', length=64, default=lambda:str(uuid.uuid4())),
Field('modified_on', 'datetime', default=request.now),
Field('nombre'),
format='%(nombre)s')

db.define_table('cosa',
Field('uuid', length=64, default=lambda:str(uuid.uuid4())),
Field('modified_on', 'datetime', default=request.now),
Field('id_propietario', length=64),
Field('nombre'),
format='%(nombre)s')

db.cosa.id_propietario.requires = IS_IN_DB(db,'persona.uuid','%(nombre)s')

if not db(db.persona.id).count():
id = uuid.uuid4()
db.persona.insert(nombre="Mximo", uuid=id)
db.cosa.insert(id_propietario=id, nombre="Silla")

Observa que en las definiciones de tabla de arriba, el valor por defecto para los dos
campos uuid estn especificados como funciones lambda, que devuelven un UUID
(convertido como cadena). La funcin lambda es llamada una vez por cada registro que
se inserte, asegurando la unicidad de cada UUID de registro, incluso si se ingresaran
mltiples registros en una nica transaccin.

Crea una accin del controlador para exportar la base de datos:


def exportar():
s = StringIO.StringIO()
db.export_to_csv_file(s)
response.headers['Content-Type'] = 'text/csv'
return s.getvalue()

Crea una accin del controlador para importar la copia previamente almacenada de la otra
base de datos y sincronizar los registros:
def importar_y_sincronizar():
formulario = FORM(INPUT(_type='file', _name='archivos'), INPUT(_type='submit'))
if formulario.process().accepted:
db.import_from_csv_file(form.vars.datos.file, unique=False)
# para cada tabla
for tabla in db.tables:
# por cada uuid, borrar todos a excepcin del ltimo actualizado
registros = db(db[tabla]).select(db[tabla].id,
db[tabla].uuid,
orderby=db[tabla].modified_on,
groupby=db[tabla].uuid)
for registro in registros:
db((db[tabla].uuid==registro.uuid)&\
(db[tabla].id!=registro.id)).delete()
return dict(formulario=formulario)

Opcionalmente, podras crear un ndice en forma manual para que cada bsqueda por uuid
sea ms rpida.
Tambin es posible utilizar XML-RPC para importar y exportar el archivo.
Si los registros hacen tienen referencias a archivos subidos, adems debes exportar e
importar el contenido de la carpeta upload. Ten en cuenta que los archivos de esa carpeta ya

estn etiquetados por UUID, de manera que no tienes que preocuparte por conflictos de
nombres o problemas con los enlaces.

HTML y XML (un objeto Table por vez)


Los objetos Rows tambin tienen un mtodo xml (como el de los ayudantes) que los serializa
como XML/HTML:
>>> registros = db(db.persona.id > 0).select()
>>> print registros.xml()
<table>
<thead>
<tr>
<th>persona.id</th>
<th>persona.nombre</th>
<th>cosa.id</th>
<th>cosa.nombre</th>
<th>cosa.id_propietario</th>
</tr>
</thead>
<tbody>
<tr class="even">
<td>1</td>
<td>Alejandro</td>
<td>1</td>
<td>Bote</td>
<td>1</td>
</tr>
...
</tbody>
</table>

Si necesitas serializar los registros en cualquier otro formato de XML usando etiquetas
personalizadas, puedes hacerlo fcilmente usando el ayudante de etiquetas para todo uso
TAG y la notacin *:
>>> registros = db(db.persona.id > 0).select()
>>> print TAG.resultado(*[TAG.registro(*[TAG.campo(r[f], _nombre=f) \
for f in db.persona.fields]) for r in registros])
<resultado>
<registro>
<campo nombre="id">1</campo>
<campo nombre="nombre">Alejandro</campo>
</registro>
...
</resultado>

Representacin de datos
La funcin export_to_csv_file acepta un argumento de par nombre-valor llamado represent .
Si se especifica como True , usar la funcin represent para cada columna al exportar los
datos en lugar del valor almacenado para el campo.
La funcin adems acepta un argumento de par nombre-valor llamado colnames que debe
contener una lista de nombres de columnas que queremos exportar. Por defecto incluye todas
las columnas.
Tanto export_to_csv_file como import_from_csv_file aceptan argumentos de par nombre-valor
que le indican al intrprete de CSV en qu formato se deben guardar o abrir los archivos:
o

delimiter : la cadena a utilizar como separador de valores (por defecto es ',')

quotechar : el carcter a usar para encerrar (quote) valores de cadena (por defecto

establece comillas dobles)


o

quoting : el sistema utilizado para el quoting, es decir, para delimitar cadenas (por

defecto es csv.QUOTE_MINIMAL )
Este es un ejemplo de uso posible:

>>> import csv


>>> registros = db(consulta).select()
>>> registros.export_to_csv_file(open('/tmp/prueba.txt', 'w'),
delimiter='|',
quotechar='"',
quoting=csv.QUOTE_NONNUMERIC)

Que se mostrara aproximadamente como sigue:


"hola"|35|"este es el texto de la descripcin"|"2009-03-03"

Para ms detalles puedes consultar la documentacin oficial de Python

[quoteall]

Cach de comandos select


El mtodo select tambin acepta un argumento cache, que por defecto es None. A efectos de
utilizar el cach, se debera especificar una tupla donde el primer elemento es el modelo de
cach (cache.ram, cache.disk, etc.), y el segundo elemento es plazo de vencimiento en
segundos.
En el ejemplo que sigue, se puede ver un controlador que hace un cach de un comando
select de la tabla log definida anteriormente. El select recupera informacin de la base de
datos con una frecuencia inferior a una consulta cada 60 segundos y almacena el resultado en
cache.ram. Si la prxima llamada a este controlador ocurre en un lapso menor a 60 segundos
desde la ltima consulta a la base de datos, simplemente recuperar la informacin
almacenada en cache.ram.
def cache_de_select():
registros = db().select(db.log.ALL, cache=(cache.ram, 60))
return dict(registros=registros)

El mtodo select acepta un argumento opcional llamado cacheable , que normalmente


es False . Cuando se especifica cacheable=True el objeto Rows resultante es serializable
aunque los objetos Row no admiten el uso de los mtodos update_record y delete_record .
Si no necesitas esos mtodos puedes acelerar los select considerablemente habilitando la
opcin cacheable:
registros = db(consulta).select(cacheable=True)

Si se ha establecido la opcin cacheable=False (por defecto) slo se hara un cach de los


resultados

de

la

base

de

datos,

pero

no

del

objeto Rows en

s.

Cuando

el

argumento cache es usado en conjunto con cacheable=True se har un cach de la totalidad


del objeto Rows y esto resultar en un cach mucho ms veloz:
registros = db(consulta).select(cache=(cache.ram, 3600), cacheable=True)

Referencias a la misma tabla y alias


Se pueden definir tablas cuyos campos hacen referencia a la tabla que los contiene, he aqu
un ejemplo:
db.define_table('persona',
Field('nombre'),
Field('id_padre', 'reference persona'),
Field('id_madre', 'reference persona'))

Observa que la notacin alternativa para el uso de un objeto Table como tipo de campo
resultar en una falla para este caso, porque utilizara una variable antes de que se haya
definido:
db.define_table('persona',
Field('nombre'),
Field('id_padre', db.persona), # no es vlido!
Field('id_madre', db.persona)) # no es vlido!

En

general, db.nombredetable y "reference

nombredetabla" son

tipos

de

campo

equivalentes, pero el ltimo es el nico admitido para referencias a la misma tabla (self
reference).
Si la tabla enlaza a s misma, entonces no es posible realizar un JOIN para recuperar un
persona y sus padres sin el uso de la instruccin "AS" de SQL. Esto se puede hacer en
web2py usando with_alias . He aqu un ejemplo:
>>> Padre = db.persona.with_alias('padre')
>>> Madre = db.persona.with_alias('madre')
>>> db.persona.insert(nombre='Mximo')

1
>>> db.persona.insert(nombre='Claudia')
2
>>> db.persona.insert(nombre='Marcos', id_padre=1, id_madre=2)
3
>>> registros = db().select(db.persona.nombre, Padre.nombre, Madre.nombre,
left=(Padre.on(Padre.id==db.persona.id_padre),
Madre.on(Madre.id==db.persona.id_madre)))
>>> for registro in registros:
print registro.persona.nombre, registro.padre.nombre, registro.madre.nombre
Massimo None None
Claudia None None
Marcos Massimo Claudia

Observa que hemos optado por separar entre:


o
o

"id_padre": el nombre de campo usado en la tabla "persona";


"padre": el alias que queremos usar para la tabla enlazada por el campo anterior; esto
se comunica a la base de datos;
"Padre": la variable a usar por web2py para hacer referencia a ese alias.

La diferencia es sutil, y no hay nada de malo en el uso del mismo nombre para las tres
instancias:
db.define_table('persona',
Field('nombre'),
Field('padre', 'reference persona'),
Field('madre', 'reference persona'))
>>> padre = db.persona.with_alias('padre')
>>> madre = db.persona.with_alias('madre')
>>> db.persona.insert(nombre='Mximo')
1

>>> db.persona.insert(nombre='Claudia')
2
>>> db.persona.insert(nombre='Marco', padre=1, madre=2)
3
>>> registros = db().select(db.persona.nombre, padre.nombre, madre.nombre,
left=(padre.on(padre.id==db.persona.padre),
madre.on(madre.id==db.persona.madre)))
>>> for registro in registros:
print registro.persona.nombre, registro.padre.nombre, registro.madre.nombre
Mximo None None
Claudia None None
Marco Mximo Claudia

Pero es importante dar cuenta de esos detalles para poder generar consultas vlidas.

Caractersticas avanzadas
Herencia de tablas
Es posible crear una tabla que contenga todos los campos de otra tabla. Basta con usar un
objeto Table en lugar de un objeto Field como argumento de define_table . Por ejemplo
db.define_table('persona', Field('nombre'))
db.define_table('doctor', db.persona, Field('especialidad'))

Adems es posible definir una tabla ficticia que no se almacene en una base de datos para
poder reutilizarla en otras instancias. Por ejemplo:
firma = db.Table(db, 'firma',
Field('created_on', 'datetime', default=request.now),
Field('created_by', db.auth_user, default=auth.user_id),
Field('modified_on', 'datetime', update=request.now),
Field('modified_by', db.auth_user, update=auth.user_id))

db.define_table('pago', Field('monto', 'double'), firma)

Este ejemplo supone que se ha habilitado la autenticacin estndar de web2py.


Observa que si usas Auth , web2py ya ha creado esa tabla por ti:
auth = Auth(db)
db.define_table('pago', Field('monto', 'double'), auth.signature)

Cuando se usa la herencia de tablas, si quieres que la tabla que hereda los campos tambin
herede sus validadores, asegrate de definir los validadores de la tabla heredada antes de
definir la tabla que los hereda.
filter_in

y filter_out

Es posible definir un filtro para cada campo que se llamarn antes de que un valor se inserte
en la base de datos para ese campo as como tambin despus de que se recupere el valor
en una consulta.
Supongamos, por ejemplo, que queremos almacenar una estructura serializable de datos de
Python en un campo en formato json. Esto se puede hacer de la siguiente forma:
>>> from simplejson import loads, dumps
>>> db.define_table('cualquierobjeto',Field('nombre'),Field('datos','text'))
>>> db.cualquierobjeto.datos.filter_in = lambda obj, dumps=dumps: dumps(obj)
>>> db.cualquierobjeto.datos.filter_out = lambda txt, loads=loads: loads(txt)
>>> miobjeto = ['hola', 'mundo', 1, {2: 3}]
>>> id = db.cualquierobjeto.insert(nombre='minombredeobjeto', datos=miobjeto)
>>> registro = db.cualquierobjeto(id)
>>> registro.datos
['hola', 'mundo', 1, {2: 3}]

Otra forma de realizar lo mismo es usando un campo de tipo SQLCustomType , como se ver
ms adelante.

Callback antes y despus de la E/S


Web2py provee de un mecanismo para registrar llamadas de retorno o callback a los que se
llamar antes y/o despus de crear, modificar o borrar registros.
Cada tabla almacena seis listas de callback:
db.mitabla._before_insert
db.mitabla._after_insert
db.mitabla._before_update
db.mitabla._after_update
db.mitabla._before_delete
db.mitabla._after_delete

Puedes registrar una funcin callback agregndola a la lista correspondiente. Un detalle a


tener en cuenta es que segn la funcionalidad utilizada, cambian las lista de argumentos de
entrada aceptadas.
Esto se explica mejor a travs de ejemplos.
>>> db.define_table('persona',Field('nombre'))
>>> def miprint(*args): print args
>>> db.persona._before_insert.append(lambda f: miprint(f))
>>> db.persona._after_insert.append(lambda f,id: miprint(f,id))
>>> db.persona._before_update.append(lambda s,f: miprint(s,f))
>>> db.persona._after_update.append(lambda s,f: miprint(s,f))
>>> db.persona._before_delete.append(lambda s: miprint(s))
>>> db.persona._after_delete.append(lambda s: miprint(s))

Donde f es un diccionario de campos pasados como argumento de los mtodos insert o


update, id es el id del nuevo registro creado y s es el objeto Set usado para los mtodos
update o delete.
>>> db.persona.insert(nombre='Juan')
({'nombre': 'Juan'},)
({'nombre': 'Juan'}, 1)

>>> db(db.persona.id==1).update(nombre='Timoteo')
(<Set (persona.id = 1)>, {'nombre': 'Timoteo'})
(<Set (persona.id = 1)>, {'nombre': 'Timoteo'})
>>> db(db.persona.id==1).delete()
(<Set (persona.id = 1)>,)
(<Set (persona.id = 1)>,)

Los valores devueltos por estos callback deberan ser None o False . Si alguno de los
callback _before_* devuelve True , anular la operacin de insercin/modificacin/borrado en
curso.
.
A veces una llamada de retorno puede necesitar ejecutar un comando update para la misma
tabla o incluso en otra tabla, y queremos evitar que las llamadas de retorno se llamen a si
mismas en forma cclica.
Para evitar este problema, los objetos Set disponen de un mtodo update_naive que funciona
en forma similar al mtodo update , pero omitiendo el uso de las llamadas de retorno para
antes y despus de la E/S de la base de datos.

Control de versiones de registros


En web2py es posible especificar que se quiere almacenar una copia de un registro cuando
este se modifique individualmente. Hay distintas formas de hacerlo, y es posible hacerlo para
todas las tablas en una sola instruccin usando la sintaxis:
auth.enable_record_versioning(db)

Esto requiere habilitar Auth y se detalla en el captulo sobre autenticacin. Tambin se puede
hacer para cada tabla individual, como se muestra a continuacin.
Tomemos como ejemplo la siguiente tabla:
db.define_table('item',
Field('nombre'),
Field('cantidad','integer'),
Field('is_active','boolean',

writable=False, readable=False, default=True))

Observa el campo booleano llamado is_active , que toma por defecto el valor True.
Podemos decirle a web2py que cree una nueva tabla (en la misma base de datos o en otra
distinta) y que almacene todas las versiones previas de cada registro en la tabla, cuando haya
modificaciones.
Esto se hace de la siguiente forma:
db.item._enable_record_versioning()

o de una forma ms explcita:


db.item._enable_record_versioning(
archive_db = db,
archive_name = 'item_archive',
current_record = 'current_record',
is_active = 'is_active')

La opcin archive_db=db le dice a web2py que almacene la tabla especial de archivado en la


misma base de datos que la tabla item . La opcin archive_name establece el nombre para la
tabla de archivado. La tabla de archivado tiene los mismos campos que la tabla original item ,
pero los campos configurados como nicos dejan de serlo (porque es necesario almacenar
distintas versiones) y tiene un campo adicional con el nombre current_record , que es una
referencia al registro actual en la tabla item .
Cuando se eliminan los registros, no se eliminan realmente. Los registros eliminados se copian
en la tabla item_archive (de igual forma que cuando se modifican) y el campo is_active se
establece como False . Al habilitarse el control de versiones de registros (record versioning),
web2py establece el parmetro custom_filter para esta tabla que oculta todos los registros en
la

tabla item para

los

cuales

el

campo is_active tiene

el

valor

False.

El

parmetro is_active en el mtodo _enable_record_versioning permite especificar el nombre


del campo usado por custom_filter para establecer si se ha eliminado el registro o no.
Los filtros de custom_filter se omiten cuando se utiliza la interfaz appadmin.

Campos comunes, y aplicaciones compartidas


db._common_fields es una lista de los campos que deberan pertenecer a toda tabla. Esta

lista tambin puede contener tablas y se interpretar como la lista de todos sus campos. Por
ejemplo, en ocasiones puedes necesitar que se agregue una firma digital a todas las tablas
que no pertenezcan a auth . En este caso, luego de db.define_tables() , pero antes de definir
cualquier otra tabla, agrega
db._common_fields.append(auth.signature)

Hay un campo especial: "request_tenant" (el inquilino, arrendatario o teniente de la solicitud).


Este campo no existe pero se puede crear y agregar a cualquier tabla (o a todas):
db._common_fields.append(Field('request_tenant',
default=request.env.http_host, writable=False))

En toda tabla que tenga el campo llamado db._request_tenant , todos los registros para todas
las consultas se filtrarn siempre con:
db.tabla.request_tenant == db.tabla.request_tenant.default

y para cada registro insertado, este campo se establece como valor por defecto. En el ejemplo
de arriba, hemos optado por la siguiente configuracin:
default = request.env.http_host

es decir, hemos configurado a nuestra app para que filtre todas las tablas en todas las
consultas con
db.tabla.request_tenant == request.env.http_host

Este simple truco nos permite convertir cualquier aplicacin en una aplicacin multitenant (compartida). Esto quiere decir que, aunque corramos una nica instancia de una app y
usemos un solo servicio de base de datos, si se accede a la app por medio de dos o ms
dominios (para el ejemplo que usamos el dominio se obtiene de request.env.http_host ) los
visitantes vern distintos datos segn el dominio de referencia. Como ejemplo, se hablar de un
sistema de tiendas en lnea para distintos dominios en una sola app y una nica base de
datos.
Puedes deshabilitar los filtros de aplicaciones compartidas usando:
registros = db(consulta, ignore_common_filters=True).select()

Filtros comunes
Un filtro comn (common filter) es la generalizacin de la caracterstica descripta ms arriba
para aplicaciones compartidas. Esto provee de una forma sencilla para evitar repetir la misma
consulta para cada caso. Consideremos como ejemplo la siguiente tabla:
db.define_table('articulo',
Field('asunto'),
Field('texto', 'text'),
Field('publico', 'boolean'),
common_filter = lambda consulta: db.articulo.publico==True
)

Toda consulta, modificacin o eliminacin para esa tabla, incluir nicamente artculos que
sean pblicos. El atributo tambin se puede cambiar en los controladores:
db.articulo._common_filter = lambda consulta: db.articulo.publico == True

Esto sirve tanto como una forma de evitar la repeticin de la expresin


"db.articulo.publico==True" en cada bsqueda de artculos, pero tambin como una mejora de
seguridad, que evita que te olvides de deshabilitar la visualizacin de articulos privados.
En caso de que quieras que algunos tems se excluyan de los filtros comunes (por ejemplo,
permitiendo que el administrador vea los artculos privados), puedes o bien quitar el filtro:
db.blog_post._common_filter = None

o ignorarlo:
db(consulta, ignore_common_filters=True).select(...)

Tipos de objeto Field personalizados (experimental)


Adems de la posibilidad de usar filter_in y filter_out , es posible definir nuevos tipos de
campo personalizados. Por ejemplo consideremos aqu un campo que contiene datos binarios
en formato comprimido:
from gluon.dal import SQLCustomType
import zlib

comprimido = SQLCustomType(
type ='text',
native='text',
encoder =(lambda x: zlib.compress(x or '')),
decoder = (lambda x: zlib.decompress(x))
)

db.define_table('ejemplo', Field('datos', type=comprimido))


SQLCustomType es un creador de tipos de campo. Su argumento type debe ser uno de los

campos estndar de web2py. Este le dice a web2py como debe manejar los valores en el nivel
de web2py. native es el nombre del campo utilizado en el motor de la base de datos. Los
nombres permitidos dependen de la base de datos. encoder es una funcin opcional de
conversin aplicada cuando los datos se almacenan y decoder es la funcin de conversin
que opera en el sentido opuesto, tambin opcional.
Esta caracterstica est marcada como experimental. En realidad ha formado parte del ncleo
de web2py desde hace tiempo y funciona normalmente, pero puede hacer que el cdigo no
sea porttil, por ejemplo, cuando el tipo nativo utilizado es especfico de la base de datos. Esta
caracterstica no funciona con Google App Engine NoSQL.

Uso de DAL sin definir tablas


Para usar la DAL desde cualquier programa en Python, basta con hacer lo siguiente:
from gluon import DAL, Field
db = DAL('sqlite://storage.sqlite', folder='ruta/a/carpeta/databases')

es decir, basta con importar DAL y Field, conectar a la base de datos especificando la carpeta
que contiene los archivos .table (en app/databases).
Para acceder a los datos y sus atributos, todava hace falta que definamos toda tabla que
necesitemos utilizar con db.define_tables(...) .
Si solo nos interesa acceder a los datos pero no a los atributos de las tablas, entonces nos
alcanza con indicarle a webp2y que lea la informacin necesaria de los metadatos en los
archivos .table, sin que sea necesario redefinir las tablas en forma explcita:

from gluon import DAL, Field


db = DAL('sqlite://storage.sqlite', folder='ruta/a/carpeta/databases',
auto_import=True))

Esto nos permite acceder a toda tabla db.tabla `sin tener que redefinirla.

PostGIS, SpatiaLite, y MS Geo (experimental)


La DAL soporta API geogrficas usando PostGIS (para PostgreSQL), spatialite (para SQLite),
y MSSQL y las extensiones Spatial. Se trata de una funcionalidad que fue patrocinada por el
proyecto Sahana e implementado por Denes Lengyel.
DAL provee de tipos de campo geometry y geography y de las siguientes funciones:
st_asgeojson (solo para PostGIS)
st_astext
st_contains
st_distance
st_equals
st_intersects
st_overlaps
st_simplify (solo para PostGIS)
st_touches
st_within
st_x
st_y

Aqu se muestran algunos ejemplos:


from gluon.dal import DAL, Field, geoPoint, geoLine, geoPolygon
db = DAL("mssql://usuario:contrasea@servidor:db")
espacial = db.define_table('espacial', Field('ubicacion','geometry()'))

A continuacin, se insertan un punto, una lnea y un polgono:

espacial.insert(ubicacion=geoPoint(1,1))
espacial.insert(ubicacion=geoLine((100,100),(20,180),(180,180)))
espacial.insert(ubicacion=geoPolygon((0,0),(150,0),(150,150),(0,150),(0,0)))

Observa que
registros = db(espacial.id>0).select()

Siempre devuelve la informacin geomtrica serializada como texto. Adems puedes hacer lo
mismo en una forma ms explcita usando st_astext() :
print db(espacial.id>0).select(espacial.id, espacial.ubicacion.st_astext())
espacial.id, espacial.ubicacion.STAsText()
1, "POINT (1 2)"
2, "LINESTRING (100 100, 20 180, 180 180)"
3, "POLYGON ((0 0, 150 0, 150 150, 0 150, 0 0))"

Puedes recuperar la representacin nativa usando st_asgeojson() (solo en PostGIS):


print db(espacial.id>0).select(espacial.id,
espacial.ubicacion.st_asgeojson().with_alias('ubicacion'))
spatial.id, ubicacion
1, [1, 2]
2, [[100, 100], [20 180], [180, 180]]
3, [[[0, 0], [150, 0], [150, 150], [0, 150], [0, 0]]]

(observa que un arreglo es un punto, un arreglo de arreglos una lnea, y un arreglo de arreglos
de arreglos es un polgono).
Aqu se muestran ejemplos para el uso de las funciones geogrficas:
consulta = espacial.ubicacion.st_intersects(geoLine((20,120),(60,160)))
consulta = espacial.ubicacion.st_overlaps(geoPolygon((1,1),(11,1),(11,11),(11,1),(1,1)))
consulta = espacial.ubicacion.st_contains(geoPoint(1,1))
print db(consulta).select(espacial.id, espacial.ubicacion)
espacial.id, espacial.ubicacion

3,"POLYGON ((0 0, 150 0, 150 150, 0 150, 0 0))"

Las distancias calculadas tambin se pueden recuperar como valores de coma flotante:
distancia = espacial.ubicacion.st_distance(geoPoint(-1,2)).with_alias('distancia')
print db(espacial.id>0).select(espacial.id, distancia)
espacial.id, distancia
1 2.0
2 140.714249456
3 1.0

Copia de datos entre distintas bases de datos


Consideremos una situacin en la que la que hemos estado usando la siguiente base de
datos:
db = DAL('sqlite://storage.sqlite')

y queremos transferir la informacin a otra base de datos usando otra cadena de conexin:
db = DAL('postgres://usuario:contrasea@localhost/midb')

Antes de hacer el cambio, queremos mover la informacin y recuperar todos los metadatos de
la nueva base de datos. Se supone que existe tal base de datos nueva y que est vaca.
Web2py provee de un script que hace esta tarea por ti:
cd web2py
python scripts/cpdb.py \
-f applications/app/databases \
-y 'sqlite://storage.sqlite' \
-Y 'postgres://usuario:contrasea@localhost/midb'

Luego de correr el script puedes simplemente cambiar la cadena de conexin en el modelo y


todo debera funcionar instantneamente, con la informacin transferida a la nueva base de
datos.

Este script provee de varias opciones de la lnea de comandos que te permiten transferir datos
de una aplicacin a otra, transferir todas las tablas o solo algunas y eliminar datos. para ms
detalles usa el siguiente comando:
python scripts/cpdb.py -h

Notas sobre la nueva DAL y los adaptadores


El cdigo fuente de la Capa de Abstraccin de la Base de Datos (DAL) se reescribi
completamente en el 2010. Si bien se mantuvo la compatibilidad hacia atrs, las
modificaciones realizadas la han hecho ms modular y fcil de extender. Aqu explicaremos
sus caractersticas destacadas.
El archivo "gluon/dal.py" define, entre otras, las siguientes clases.
ConnectionPool
BaseAdapter (extensin de ConnectionPool)
Row
DAL
Reference
Table
Expression
Field
Query
Set
Rows

Su uso se ha explicado en las secciones previas, salvo el caso de BaseAdapter . Cuando los
mtodos de un objeto Table o Set necesitan comunicarse con la base de datos, delegan a los
mtodos del adaptador la tarea de generar el SQL y/o la llamada a una funcin.
Por ejemplo:
db.mitabla.insert(micampo='mivalor')

llama a
Table.insert(micampo='micampo')

que remite la instruccin al adaptador, devolviendo:


db._adapter.insert(db.mitabla, db.mitabla._listify(dict(micampo='mivalor')))

Aqu, db.mitabla._listify convierte el diccionario con los argumentos en una lista de


pares (campo,

valor) y

llama

al

mtodo

de

insercin insert del

objeto adapter . db._adapter hace algo similar a lo siguiente:


consulta = db._adapter._insert(db.mitabla, lista_de_campos)
db._adapter.execute(consulta)

donde la primer lnea genera la consulta y la segunda la ejecuta.


BaseAdapter define la interfaz para todos los adaptadores.

"gluon/dal.py", al tiempo de esta edicin, contiene los siguientes adaptadores:


SQLiteAdapter extensin de BaseAdapter
JDBCSQLiteAdapter extensin de SQLiteAdapter
MySQLAdapter extensin de BaseAdapter
PostgreSQLAdapter extensin de BaseAdapter
JDBCPostgreSQLAdapter extensin de PostgreSQLAdapter
OracleAdapter extensin de BaseAdapter
MSSQLAdapter extensin de BaseAdapter
MSSQL2Adapter extensin de MSSQLAdapter
FireBirdAdapter extensin de BaseAdapter
FireBirdEmbeddedAdapter extensin de FireBirdAdapter
InformixAdapter extensin de BaseAdapter
DB2Adapter extensin de BaseAdapter
IngresAdapter extensin de BaseAdapter
IngresUnicodeAdapter extensin de IngresAdapter
GoogleSQLAdapter extensin de MySQLAdapter
NoSQLAdapter extensin de BaseAdapter
GoogleDatastoreAdapter extensin de NoSQLAdapter

CubridAdapter extensin de MySQLAdapter (experimental)


TeradataAdapter extensin de DB2Adapter (experimental)
SAPDBAdapter extensin de BaseAdapter (experimental)
CouchDBAdapter extensin de NoSQLAdapter (experimental)
MongoDBAdapter extensin de NoSQLAdapter (experimental)
IMAPAdapter extensin de NoSQLAdapter (experimental)

que sobreescriben el comportamiento de BaseAdapter .


Cada adaptador tiene ms o menos esta estructura:
class MySQLAdapter(BaseAdapter):

# especifica qu controlador usa


driver = globals().get('pymysql', None)

# traduce tipos de campo de web2py a


# los tipos de campo de la base de datos
types = {
'boolean': 'CHAR(1)',
'string': 'VARCHAR(%(length)s)',
'text': 'LONGTEXT',
...
}

# conectar a la base de datos usando el controlador


def __init__(self, db, uri, pool_size=0, folder=None, db_codec ='UTF-8',
credential_decoder=lambda x:x, driver_args={},
adapter_args={}):
# analiza la cadena de conexin uri y almacena los
# parmetros en driver_args

...

# define una funcin para conectar a la base de datos


def connect(driver_args=driver_args):
return self.driver.connect(**driver_args)
# la agrega al cach de conexiones
self.pool_connection(connect)

# configura parmetros opcionales al establecerse la conexin


self.execute('SET FOREIGN_KEY_CHECKS=1;')
self.execute("SET sql_mode='NO_BACKSLASH_ESCAPES';")

# sobreescribe los mtodos bsicos de BaseAdapter segn


# sea necesesario
def lastrowid(self, table):
self.execute('select last_insert_id();')
return int(self.cursor.fetchone()[0])

Debera ser fcil agregar nuevos adaptadores si se toman como ejemplo los adaptadores
incorporados.
Cuando se crea la instancia db :
db = DAL('mysql://...')

el prefijo en la cadena uri define el adaptador a utilizar. Los adaptadores asociados se define
en el siguiente diccionario, que tambin se encuentra en "gluon/dal.py":
ADAPTERS = {
'sqlite': SQLiteAdapter,
'sqlite:memory': SQLiteAdapter,
'mysql': MySQLAdapter,
'postgres': PostgreSQLAdapter,

'oracle': OracleAdapter,
'mssql': MSSQLAdapter,
'mssql2': MSSQL2Adapter,
'db2': DB2Adapter,
'teradata': TeradataAdapter,
'informix': InformixAdapter,
'firebird': FireBirdAdapter,
'firebird_embedded': FireBirdAdapter,
'ingres': IngresAdapter,
'ingresu': IngresUnicodeAdapter,
'sapdb': SAPDBAdapter,
'cubrid': CubridAdapter,
'jdbc:sqlite': JDBCSQLiteAdapter,
'jdbc:sqlite:memory': JDBCSQLiteAdapter,
'jdbc:postgres': JDBCPostgreSQLAdapter,
'gae': GoogleDatastoreAdapter, # discouraged, for backward compatibility
'google:datastore': GoogleDatastoreAdapter,
'google:sql': GoogleSQLAdapter,
'couchdb': CouchDBAdapter,
'mongodb': MongoDBAdapter,
'imap': IMAPAdapter
}

luego, la cadena uri es analizada con ms detalle por el adaptador.


Para cualquier adaptador puedes sustituir el controlador:
import MySQLdb as mysqldb
from gluon.dal import MySQLAdapter
MySQLAdapter.driver = mysqldb

para este caso, mysqldb debe ser ese mdulo que implementa un mtodo .connect(). Puedes
especificar controladores opcionales y argumentos de adaptadores:
db =DAL(..., driver_args={}, adapter_args={})

Detalles a tener en cuenta


SQLite no tiene soporte para el descarte o alteracin de columnas. Esto quiere decir que las
migraciones de web2py funcionarn hasta cierto punto. Si eliminas un campo de una tabla, la
columna permanece en la base de datos pero no ser visible para web2py. Si decides
restablecer la columna, web2py intentar crearla nuevamente y fallar. En este caso debes
establecer fake_migrate=True para que los metadatos se generen nuevamente sin intentar
agregar la columna nuevamente. Adems, y por la misma razn, SQLite no detecta ningn
cambio en los tipos de columna. Si insertas un nmero en un campo tipo string, se
almacenar como cadena. Si luego cambias el modelo y reemplazas el tipo "string" por
"integer", SQLite continuar manteniendo el nmero como cadena y esto puede causar un
problema cuando trates de extraer la informacin.
MySQL no tiene soporte para comandos ALTER TABLE mltiples en una sola transaccin.
Esto significa que todo proceso de migracin se separa en mltiples instrucciones que se
aplican sucesivamente. Si ocurriera algo que causara una falla, probablemente fracasara la
migracin (los metadatos de web2py dejaran de estar sincronizados con las estructuras de
tablas reales de la base de datos). Esto es bastante desafortunado, pero se puede prevenir
(migrando una tabla por vez) y tambin se puede reparar a posteriori (restablecer el modelo de
web2py al correspondiente a la estructura real de la base de datos,
establecer fake_migrate=True y luego de que los metadatos se hayan generado nuevamente,
configurar fake_migrate=False y migrar la tabla nuevamente).
Google SQL tiene los mismos problemas que MySQL y ms. En especial los metadatos en s
se deben almacenar en la base de datos en uan tabla que no sea migrada por web2py. Esto
se debe a que Google App Engine tiene un sistema de archivos de solo lectura. Las
migraciones de web2py en Google:SQL combinadas con el problema descripto para MySQL
pueden resultar en la corrupcin de los metadatos. Nuevamente, es posible prevenir este
problema (migrando la tabla y de inmediato estableciendo migrate=False para que la tabla de
metadatos ya no se pueda modificar) o se puede arreglar despus de una falla (accediendo a
la base de datos con el panel administrativo de Google o dashboard y eliminando toda entrada
incorrecta de la tabla llamada web2py_filesystem ).
MSSQL no tiene soporte para la instruccin SQL OFFSET. Por lo tanto la base de datos no
puede realizar paginacin. Cuando se hace un limitby=(a, b) , web2py recuperar los
primeros b registros y descartar el primer a . Esto puede resultar en un excesivo uso de
recursos (overhead) si se compara con otros motores de bases de datos.

Oracle tampoco tiene soporte para paginacin. No contempla paginacin o las instrucciones
OFFSET
y
LIMIT.
web2py
obtiene
la
paginacin
transformando
la
expresin db(...).select(limitby=(a, b)) en un conjunto complejo de comandos select triples
anidados (segn lo sugerido por la documentacin de Oracle). Esto funciona para comandos
select simples pero puede fallar en operaciones que utilicen alias y join.
MSSQL tiene problemas con las referencias circulares en tablas que tengan ONDELETE
CASCADE. Esta es una falla de MSSQL y se puede resolver configurando el atributo ondelete
para todos los campos de tipo reference como "NO ACTION". Adems puedes hacerlo para
todas las tablas con una nica instruccin antes de definir las tablas:
db = DAL('mssql://....')
for clave in ['reference','reference FK']:
db._adapter.types[clave]=db._adapter.types[clave].replace(
'%(on_delete_action)s','NO ACTION')

MSSQL tambin tiene problemas con los argumentos que se pasan a la instruccin DISTINCT
y por lo tanto, esto funciona,
db(consulta).select(distinct=True)

pero esto no
db(consulta).select(distinct=db.mitabla.micampo)

Google
uso del
campos
web2py

NoSQL (Datastore) no admite el uso de join, joins a izquierda, sumas, expresiones,


operador OR para ms de una tabla, el uso del operador like en bsquedas en
tipo "text". Las transacciones son limitadas y no son provistas automticamente por
(debes usar run_in_transaction de la API de Google, que est descripta en la

documentacin en lnea de Google App Engine). Google adems limita el nmero de registros
que se pueden recuperar por cada consulta (al tiempo de esta edicin era 1000 registros). En
el sistema de almacenamiento Datastore de Google, los ID son enteros pero no son
secuenciales. En SQL el tipo "list:string" se traduce como tipo "text", pero en el Datastore de
Google se traduce como objeto ListStringProperty . En forma similar, "list:integer" y
"list:reference" se traducen como ListProperty . Esto hace que las bsquedas de contenido
dentro de esos campos sean ms eficientes para Google NoSQL que para las bases de datos
SQL.

Formularios y Validadores
Hay cuatro maneras distintas de crear formularios en web2py:

FORM provee de una implementacin de bajo nivel con respecto a los ayudantes. Un

objeto FORM se puede serializar como HTML y tiene control de los campos que
contiene. Los objetos FORM saben como validar los campos del formulario enviados.
SQLFORM provee de una API de alto nivel para generar formularios de creacin,

actualizacin y eliminacin a partir de una tabla de la base de datos.


SQLFORM.factory es una capa de abstraccin que opera sobre SQLFORM para

aprovechar las funcionalidades para creacin de formularios incluso cuando no se


especifica una base de datos. Crea un formulario muy similar a SQLFORM a partir de la
descripcin de una tabla pero sin necesidad de crear la tabla en la base de datos.
Mtodos CRUD . Su funcionamiento es equivalente al de SQLFORM y se basan en

SQLFORM, pero proveen de una notacin ms compacta.


Todos estos formularios pueden realizar autocomprobaciones y, si un campo de datos no pasa
la validacin, pueden modificarse a s mismos en forma automtica y agregar informes de
errores. Los formularios se pueden consultar para examinar los valores de validacin y para
recuperar los mensajes de error que se hayan generado al procesar los datos.
Se pueden insertar elementos al HTML en forma programtica o recuperar secciones
especficas del elemento formulario usando ayudantes.
FORM y SQLFORM son ayudantes y se pueden manipular en forma similar que con los

objetos DIV . Por ejemplo, puedes establecer el estilo del formulario:


formulario = SQLFORM(...)
formulario['_style']='border:1px solid black'

FORM

Tomemos como ejemplo una aplicacin de prueba con el siguiente controlador "default.py":
def mostrar_formulario():
return dict()

y la vista asociada "default/mostrar_formulario.html":


{{extend 'layout.html'}}
<h2>Formulario de ingreso de datos</h2>
<form enctype="multipart/form-data"
action="{{=URL()}}" method="post">
Tu nombre:

<input name="nombre" />


<input type="submit" />
</form>
<h2>Variables enviadas</h2>
{{=BEAUTIFY(request.vars)}}

Este es un formulario corriente en HTML que le pide al usuario su nombre. Cuando se


completa el formulario y se hace clic en el botn de enviar, el formulario se autoenva ( selfsubmit), y la variable request.vars.nombre junto con el valor del campo completado se
muestran en la parte inferior.
Puedes generar el mismo formulario usando ayudantes. Esto se puede hacer tanto en la vista
como en la accin. Como web2py procesa el formulario en la accin, es preferible que lo
definamos all.
Este es el nuevo controlador:
def mostrar_formulario():
formulario=FORM('Tu nombre:', INPUT(_name='nombre'), INPUT(_type='submit'))
return dict(formulario=formulario)

y la vista asociada "default/mostrar_formulario.html":


{{extend 'layout.html'}}
<h2>Formulario de ingreso de datos</h2>
{{=formulario}}
<h2>Variables enviadas</h2>
{{=BEAUTIFY(request.vars)}}

El cdigo hasta aqu es equivalente al anterior, pero el formulario se crea por medio de la
instruccin {{=formulario}} , que serializa el objeto FORM .
Ahora le damos al ejemplo un nivel ms de complejidad al agregar la validacin y
procesamiento del formulario.
Cambia el controlador como sigue:
def mostrar_formulario():
formulario=FORM('Tu nombre:',
INPUT(_name='nombre', requires=IS_NOT_EMPTY()),
INPUT(_type='submit'))

if formulario.accepts(request,session):
response.flash = 'formulario aceptado'
elif formulario.errors:
response.flash = 'el formulario tiene errores'
else:
response.flash = 'por favor complete el formulario'
return dict(formulario=formulario)

y la vista asociada "default/mostrar_formulario.html":


{{extend 'layout.html'}}
<h2>Formulario de ingreso</h2>
{{=formulario}}
<h2>Variables enviadas</h2>
{{=BEAUTIFY(request.vars)}}
<h2>Variables aceptadas</h2>
{{=BEAUTIFY(formulario.vars)}}
<h2>Errores en el formulario</h2>
{{=BEAUTIFY(formulario.errors)}}

Observa que:
o

En la accin, agregamos el validador requires=IS_NOT_EMPTY() para el campo de


ingreso de datos "nombre".

En la accin, hemos agregado una llamada a formulario.accepts(..)

En la vista, mostramos formulario.vars y formulario.errors as como tambin el


formulario y request.vars .

Todo el trabajo lo hace el mtodo accepts del objeto formulario . El mtodo filtra los datos
de request.vars segn

los

requerimientos

validadores). accepts almacena

aquellas

declarados

variables

que

(por

medio

pasan

la

de

los

validacin

en formulario.vars . Si un campo no cumple con un requisito, el validador correspondiente


devolver

un

error

el

error

Tanto formulario.vars como formulario.errors son

se

almacenar

en formulario.errors .

objetos gluon.storage.Storage similares

a request.vars . El primero contiene los valores que pasaron la validacin, por ejemplo:

formulario.vars.nombre = "Maximiliano"

El otro contiene los errores, por ejemplo:


formulario.errors.name = "No puede estar vaco!"

Los argumentos de entrada aceptados del mtodo accepts son los siguientes:
formulario.accepts(vars, session=None, formname='default',
keepvalues=False, onvalidation=None,
dbio=True, hideerror=False):

El significado de los parmetros opcionales se explicarn en las prximas secciones.


El

primer

argumento

simplemente request .

puede
Este

ser request.vars o request.get_vars o request.post_vars o

ltimo

es

equivalente

aceptar

como

valores

de

entrada request.post_vars .
La funcin accepts devuelve True si el formulario se fue aceptado y False en caso contrario.
Un formulario no se aceptar si tiene errores o cuando no se haya enviado (por ejemplo, la
primera vez que se muestre).
As es como se ver la pgina la primera vez que se muestre:

As se ve cuando se envan datos invlidos:

Si se envan datos correctos, la pgina mostrar lo siguiente:

Los mtodos process y validate


Esta instruccin
formulario.accepts(request.post_vars, session,...)

se puede abreviar con el siguiente atajo:

formulario.process(...).accepted

Esta ltima instruccin no requiere los argumentos request y session (aunque los puedes
especificar opcionalmente). Tambin es distinto a accepts porque devuelve el formulario en s.
Internamente process llama a accepts y le pasa los parmetros recibidos. El valor devuelto
por accepts se almacena en formulario.accepted .
La funcin process toma algunos parmetros extra no especificados para accepts :
o

message_onsuccess

onsuccess : cuando es igual a 'flash' (por defecto) y el formulario se acepta, mostrar

un mensaje emergente con el valor de message_onsuccess


o

message_onfailure

onfailure : cuando es igual a 'flash' (por defecto) y el formulario no pasa la validacin,

mostrar el valor de message_onfailure


o

next : especifica la redireccin en caso de que se acepte el formulario.

onsuccess y onfailure pueden

ser

funciones

como

por

ejemplo lambda

formulario:

hacer_algo(formulario) .
formulario.validate(...)

es un atajo para
formulario.process(...,dbio=False).accepted

Campos ocultos
Cuando el formulario anterior sea serializado por {{=form}} , y luego de la llamada al
mtodo accepts , se mostrar de la siguiente forma:
<form enctype="multipart/form-data" action="" method="post">
tu nombre:
<input name="nombre" />
<input type="submit" />
<input value="783531473471" type="hidden" name="_formkey" />
<input value="default" type="hidden" name="_formname" />
</form>

Observa la presencia de dos campos ocultos: "_formkey" y "_formname". Estos campos son
originados con la llamada a accepts y cumplen dos roles importantes:
o

El campo oculto llamado "_formkey" es una clave nica por formulario usada por
web2py para evitar sobreemisin (double submission) de formularios. El valor de esta
clave o token se genera cuando el formulario se serializa y es almacenado en el
objeto session Cuando el formulario se enva, este valor debe coincidir, o de lo
contrario accepts devolver False sin errores, como si el formulario no se hubiera
enviado. Esto se debe a que web2py no puede determinar si el formulario se envi en
forma correcta.

El campo llamado "_formname" es generado por web2py para asignarle un nombre


especfico, pero este nombre se puede sobrescribir. Este campo es necesario para
permitir el procesamiento de mltiples formularios en una pgina. web2py diferencia los
distintos formularios enviados segn sus nombres.
Los campos ocultos adicionales se especifican como FORM(.., hidden=dict(...)) .

El rol de estos campos ocultos y su uso en pginas con uno o ms formularios personalizados
se trata con ms detalle en otras secciones de este captulo.
Si el formulario de anterior es enviado con un campo "nombre" vaco, el formulario no pasa la
validacin. Cuando el formulario es serializado nuevamente presenta lo siguiente:
<form enctype="multipart/form-data" action="" method="post">
your name:
<input value="" name="nombre" />
<div class="error">No puede estar vaco!</div>
<input type="submit" />
<input value="783531473471" type="hidden" name="_formkey" />
<input value="default" type="hidden" name="_formname" />
</form>

Observa la presencia de una clase DIV "error" en el formulario personalizado. web2py inserta
los mensajes de error en el formulario para notificar al visitante sobre los campos que no
pasaron la validacin. El mtodo accepts , al enviarse el formulario, determina si de hecho ha
sido enviado, comprueba si el campo "nombre" est vaco y si es obligatorio, y si es as,
inserta el mensaje de error del validador en el formulario.
La plantilla bsica "layout.html" puede manejar elementos DIV de la clase "error". El diseo por
defecto usa efectos de jQuery para hacer que los errores aparezcan y se desplieguen con un
fondo rojo. Para ms detalles consulta el captulo 11.

keepvalues

El argumento opcional keepvalues le dice a web2py qu hacer cuando un formulario es


aceptado no hay redireccin, para que se muestre el mismo formulario nuevamente. Por
defecto se reinicia el formulario. Si se establece keepvalues como True , el formulario se
preconfigura con los valores insertados previamente. Esto resulta til cuando tienes un
formulario que se usar en forma sucesiva para ingresar nuevos registros similares. Cuando el
argumento dbio es False , web2py no realizar actualizaciones o inserciones en la base de
datos luego de aceptarse el formulario. Si hideerror se establece como True y el formulario
contiene errores, estos no se mostrarn al devolver el formulario al cliente (los informes de
errores recuperados con formulario.errors dependern de otras personalizaciones y cdigo
fuente utilizados en la app). El argumento onvalidation se explica a continuacin.
onvalidation

El argumento onvalidation puede ser None o puede ser una funcin que toma un formulario
y no devuelve nada. Esa funcin debera llamarse pasando el formulario como argumento una
vez que el formulario haya validado (es decir, que pase la validacin) y antes de todo proceso
posterior. Esta es una tcnica que tiene mltiples usos. Se puede usar, por ejemplo para
realizar comprobaciones adicionales del formulario y posiblemente agregar informes de
errores. Tambin se puede utilizar para realizar clculos con los valores de algunos campos
segn los valores de otros. Se puede usar para activar alguna accin complementaria (como
por ejemplo enviar correo electrnico) antes de que el registro se genere o actualice.
He aqu un ejemplo:
db.define_table('numeros',
Field('a', 'integer'),
Field('b', 'integer'),
Field('c', 'integer', readable=False, writable=False))

def procesar_formulario(formulario):
c = formulario.vars.a * form.vars.b
if c < 0:
formulario.errors.b = 'a*b no puede ser negativo'
else:
formulario.vars.c = c

def insertar_numeros():
formulario = SQLFORM(db.numeros)
if formulario.process(onvalidation=procesar_formulario).accepted:
session.flash = 'registro insertado'
redirect(URL())
return dict(formulario=formulario)

Detectar un cambio del registro


Cuando se completa un formulario para modificar un registro de la base de datos existe la
posibilidad de que otro usuario est actualmente modificando el mismo registro. Entonces,
cuando guardamos el registro debemos comprobar posibles conflictos. Esto es posible de la
siguiente forma:
db.define_table('perro',Field('nombre'))

def modificar_perro():
perro = db.perro(request.args(0)) or redirect(URL('error'))
formulario=SQLFORM(db.perro, perro)
formulario.process(detect_record_change=True)
if formulario.record_changed:
# hacer algo aqu
elif formulario.accepted:
# hacer algo ms
else:
# no hacer nada
return dict(formulario=formulario)

Formularios y redireccin
Una forma corriente de usar formularios es a travs del autoenvo, para que las variables
enviadas para validacin se procesen en la misma accin que gener el formulario. Una vez
que el formulario se ha aceptado, se suele mostrar la misma pgina nuevamente (algo que
haremos aqu con el nico propsito de que el ejemplo sea ms sencillo). Es ms usual sin
embargo redirigir al visitante a la prxima pgina (comnmente denominada next).
Este es el nuevo ejemplo del controlador:

def mostrar_formulario():
formulario = FORM('Tu nombre:',
INPUT(_name='nombre', requires=IS_NOT_EMPTY()),
INPUT(_type='submit'))
if formulario.process().accepted:
session.flash = 'formulario aceptado'
redirect(URL('next'))
elif formulario.errors:
response.flash = 'el formulario tiene errores'
else:
response.flash = 'por favor complete el formulario'
return dict(formulario=formulario)

def next():
return dict()

Para poder establecer un mensaje emergente en la prxima pgina en lugar de la actual


debes usar session.flash en lugar de response.flash web2py convierte el anterior en este
ltimo luego de la redireccin. Ten en cuenta que para poder usar session.flash no debes
usar session.forget() .

Mltiples formularios por pgina


El contenido de esta seccin es vlido tanto para el objeto FORM como para SQLFORM . Es
posible manejar mltiples formularios por pgina, pero debes procurar que web2py los pueda
diferenciar. Si son generados por SQLFORM a partir de distintas tablas, entonces web2py les
asigna distintos nombres en forma automtica; de lo contrario debes asignarles un nombre a
cada uno en forma explcita. He aqu un ejemplo:
def dos_formularios():
formulario1 = FORM(INPUT(_name='nombre', requires=IS_NOT_EMPTY()),
INPUT(_type='submit'))
formulario2 = FORM(INPUT(_name='nombre', requires=IS_NOT_EMPTY()),
INPUT(_type='submit'))

if formulario1.process(formname='formulario_uno').accepted:
response.flash = 'formulario uno aceptado'
if formulario2.process(formname='formulario_dos').accepted:
response.flash = 'formulario dos aceptado'
return dict(formulario1=formulario1, formulario2=formulario2)

y esta es la salida que produce:

Cuando un visitante enva un formulario1 vaco, slo ese formulario muestra un error; si el
visitante enva un formulario2 vaco, slo el formulario2 muestra el mensaje de error.

Compartiendo formularios
El contenido de esta seccin es vlido tanto para el objeto FORM como para SQLFORM . Lo
que aqu se trata es posible pero no recomendable, ya que siempre es buena prctica el uso
de formularios autoenviados. A veces, sin embargo, no tienes alternativa, porque la accin que
enva el formulario y la accin que lo recibe pertenecen a distintas aplicaciones.
Es posible generar un formulario que se enva a otra accin. Esto se hace especificando el
URL de la accin que procesar los atributos del objeto FORM o SQLFORM . Por ejemplo:
formulario = FORM(INPUT(_name='nombre', requires=IS_NOT_EMPTY()),
INPUT(_type='submit'), _action=URL('pagina_dos'))

def pagina_uno():
return dict(formulario=formulario)

def pagina_dos():
if formulario.process(session=None, formname=None).accepted:
response.flash = 'formulario aceptado'
else:
response.flash = 'hubo un error en el formulario'
return dict()

Observa que como en las dos acciones, "pagina_uno" y "pagina_dos" se usa el mismo
formulario, lo hemos definido slo una vez ubicndolo fuera de toda accin, para evitar la
redundancia del cdigo fuente. La parte comn de cdigo al inicio de un controlador se ejecuta
cada vez antes de pasarle el control a la accin invocada.
Como "pagina_uno" no llama a process (ni a accepts ), el formulario no tiene nombre ni clave,
por

lo

que

debes

pasar

el

argumento session=None y

establecer formname=None en process , o el formulario no validar cuando "pagina_dos" lo


reciba.

Agregando botones a los FORM

Normalmente un formulario viene con un nico botn de enviar. Es comn la necesidad de


agregar un botn de "regresar" que en lugar de enviar el formulario, dirija al usuario a otra
pgina diferente.
Esto se puede hacer con el mtodo add_button :
formulario.add_button('Volver', URL('otra_pagina'))

Puedes agregar ms de un botn al formulario. Los argumentos de add_button son el valor


(es decir, el texto que se muestra) y el url de la direccin asociada.

Otros detalles sobre la manipulacin de FORM


Como se trata en el captulo de Vistas, un FORM es un ayudante de HTML. Los ayudantes se
pueden manipular como listas de Python y como diccionarios; esto permite realizar
modificaciones y agregar caractersticas en los formularios al vuelo.
SQLFORM

Ahora pasamos a un nivel ms avanzado agregndole un modelo a la aplicacin:


db = DAL('sqlite://storage.sqlite')
db.define_table('persona', Field('nombre', requires=IS_NOT_EMPTY()))

Modificamos el controlador de la siguiente forma:


def mostrar_formulario():
formulario = SQLFORM(db.person)
if formulario.process().accepted:
response.flash = 'formulario aceptado'
elif formulario.errors:
response.flash = 'el formulario tiene errores'
else:
response.flash = 'por favor complete el formulario'
return dict(formulario=formulario)

La vista no necesita modificaciones.


En el nuevo controlador, no necesitas crear un FORM , ya que el constructor de SQLFORM ha
creado uno a partir de la tabla db.persona definida en el modelo. Este nuevo formulario,
cuando se serializa, se ve de esta forma:
<form enctype="multipart/form-data" action="" method="post">

<table>
<tr id="persona_nombre__row">
<td><label id="persona_nombre__label"
for="persona_nomobre">Tu nombre: </label></td>
<td><input type="text" class="string"
name="nombre" value="" id="persona_nombre" /></td>
<td></td>
</tr>
<tr id="submit_record__row">
<td></td>
<td><input value="Submit" type="submit" /></td>
<td></td>
</tr>
</table>
<input value="9038845529" type="hidden" name="_formkey" />
<input value="persona" type="hidden" name="_formname" />
</form>

El formulario generado automticamente es ms complejo que el formulario de bajo nivel


previo. En primer lugar, este contiene una tabla con registros o row html, y cada registro de la
tabla tiene tres columnas. La primer columna contiene los rtulos o label (definidas
en db.person ), la segunda columna contiene los campos para ingreso de datos (y en caso de
errores, los mensajes), y la tercer columna es opcional y por lo tanto vaca (se puede
completar con campos del constructor de SQLFORM ).
Todas etiquetas del formulario tienen sus nombres compuestos a partir de los nombres de la
tabla y de sus campos. Esto permite fcilmente personalizar el formulario usando CSS y
JavaScript. Esta caracterstica se trata con ms detalle en el Captulo 11.
Ms importante es el hecho de que el mtodo accepts facilita notablemente las cosas. Como
en el caso anterior, realiza la validacin de los datos ingresados, pero adems, si los datos
pasan la validacin, tambin realiza la insercin del nuevo registro y almacena
en formulario.vars.id el id nico asociado al registro.
El objeto SQLFORM tambin se encarga del manejo automtico de los campos tipo "upload"
para subir archivos y almacenarlos en la carpeta con el mismo nombre (luego de cambiar en

forma segura su nombre para evitar conflictos y prevenir los ataques de tipo directory
traversal) y almacena sus nombres (sus nuevos nombres) en el campo correspondiente en la
base de datos. Una vez que se procese el formulario, el nuevo nombre de archivo estar
disponible en formulario.vars.filename , para se pueda recuperar fcilmente despus de
almacenarlo en el servidor.
Los SQLFORM realizan conversiones por defecto como por ejemplo mostrar los valores
"booleanos" como elementos checkbox y convertir los campos tipo "text" en
elementos textarea. Los campos para los que se haya restringido a un conjunto de valores
especfico (por medio de una lista o un conjunto de registros de la basse de datos) se
muestran como listas desplegables y los campos "upload" incluyen links que permiten la
descarga de los archivos subidos. Los campos tipo "blob" se ocultan por defecto, ya que
normalmente son manejados por mtodos especiales, como veremos ms adelante.
Tomemos el siguiente modelo como ejemplo:
db.define_table('persona',
Field('nombre', requires=IS_NOT_EMPTY()),
Field('casado', 'boolean'),
Field('sexo', requires=IS_IN_SET(['Masculino', 'Femenino', 'Otro'])),
Field('perfil', 'text'),
Field('imagen', 'upload'))

En este caso, SQLFORM(db.persona) genera el formulario que se muestra abajo:

El constructor de SQLFORM permite varias personalizaciones, como por ejemplo mostrar


nicamente un subconjunto de los campos, cambiar las etiquetas o labels, agregar valores en
la tercer columna opcional, o crear formularios para actualizar y borrar (UPDATE y DELETE)
en lugar de los formularios de insercin (INSERT). SQLFORM es el objeto de la API ms
completo y sinttico para automatizar tareas de web2py.

La clase SQLFORM se define en "gluon/sqlhtml.py". Se puede extender fcilmente


sobrescribiendo su mtodo xml , que es el mtodo que serializa el objeto, para cambiar su
salida.
La lista de argumentos del constructor de SQLFORM es la siguiente:
SQLFORM(table, record=None,
deletable=False, linkto=None,
upload=None, fields=None, labels=None,
col3={}, submit_button='Submit',
delete_label='Check to delete:',
showid=True, readonly=False,
comments=True, keepopts=[],
ignore_rw=False, record_id=None,
formstyle='table3cols',
buttons=['submit'], separator=': ',
**attributes)

El segundo parmetro opcional convierte el formulario de insercin en un formulario de


modificacin para el registro especificado (ver la prxima seccin).

Si deletable se establece como True , el formulario de modificacin muestra el cuadro


de confirmacin "Marcar para eliminar". El valor de la etiqueta de este campo se
establece a travs del argumento delete_label .

submit_button establece el texto del botn para enviar el formulario.

id_label establece la etiqueta del campo "id"

El "id" del registro no se muestra si showid se establece como False .

fields es una lista opcional de nombres de campos que quieres mostrar. Si la lista se

especifica, slo se muestran los campos en la lista. Por ejemplo:


fields = ['nombre']

labels es un diccionario de etiquetas para los campos. Los nombres o key son

nombres de campos y lo que se muestra es el valor correspondiente a ese nombre en el


diccionario. Si no se especifica una etiqueta, web2py la obtiene a partir del nombre del

campo (usa maysculas para las letras iniciales de los nombres de campo y reemplaza
los subguiones con espacios). Por ejemplo:
labels = {'nombre':'Tu nombre completo:'}

col3 es un diccionario de valores para la tercer columna. Por ejemplo:

col3 = {'nombre':A('Qu es esto?',


_href='http://www.google.com/search?q=define:name')}

linkto y upload son URL opcionales asociados a controladores definidos por el

usuario que permiten que el formulario maneje campos de tipo reference. Esto se tratar
con ms detalle en otra seccin.
o

readonly . Si se especifica True, muestra un formulario de slo lectura

comments . Si se especifica False, no muestra los comentarios en col3

ignore_rw . Normalmente, para un formulario de creacin o modificacin, slo se

muestran los campos marcados como readable=True. Al especificar ignore_rw=True se


hace que esas restricciones se ignoren, y que se muestren todos los campos. Esto es
usado sobre todo en la interfaz appadmin para mostrar todos los campos para cada
tabla, sobrescribiendo las opciones del modelo.
o

formstyle determina el estilo que se usar cuando se serializa el formulario en html.

Puede ser "table3cols" (por defecto), "table2cols" (un registro por etiqueta y un registro
por cada campo de ingreso de datos), "ul" (crea una lista sin orden con campos de
ingreso de datos), "divs" (genera el formulario usando elementos div aptos para css y
personalizaciones avanzadas). formstyle tambin puede ser una funcin que recibe los
parmetros id_registro, etiqueta_campo, widget_campo y comentario_campo y devuelve
un objeto TR().
o

es una lista de campos o botones INPUT s o TAG.BUTTON (aunque en realidad podra


ser cualquier combinacin de ayudantes) que se agregarn a un DIV en el lugar donde
ira el botn para enviar.

el separator establece la cadena que se usa como separador para las etiquetas y los
campos de ingreso de datos.

Los atributos opcionales attributes son argumentos que comienzan con subguin que
puedes pasar al elemento html FORM que es creado por el objeto SQLFORM . Por
ejemplo:

_action = '.'
_method = 'POST'

Hay un atributo especial llamado hidden . Cuando se especifica hidden como un diccionario,
sus tems se traducen en campos INPUT de tipo "hidden" (consulta los ejemplos para el
ayudante FORM del Captulo 5).
formulario = SQLFORM(....,hidden=...)

hace, como es de esperarse, que los campos ocultos se pasen junto con el envo del
formulario.
Por

defecto, formulario.accepts(...) no

leer

los

campos

ocultos

ni

los

pasar

formulario.vars. Esto se debe a cuestiones de seguridad. Los campos ocultos podran ser
manipulados en una forma no esperada. Por lo tanto, debes pasar en forma explcita los
campos ocultos desde la solicitud al formulario:
formulario.vars.a = request.vars.a
formulario = SQLFORM(..., hidden=dict(a='b'))

SQLFORM

e insert/update/delete

SQLFORM crea un nuevo registro de la base de datos cuando el formulario se acepta.

Suponiendo un formulario=SQLFORM(db.prueba) , entonces el id del ltimo registro creado se


podr recuperar con miformulario.vars.id .
Si especificas un registro como segundo argumento opcional en el constructor de SQLFORM ,
el formulario se convierte en un formulario de modificacin o UPDATE form para ese registro.
Esto quiere decir que cuando el formulario se enve, no se crear un nuevo registro, sino que
se actualizar el registro existente. Si estableces la opcin deletable=True , el formulario de
modificacin mostrar un checkbox(cuadro para confirmar una opcin). Si se marca el
checkbox, el registro se eliminar.

Si se enva un formulario y la opcin checkbox est marcada, el


atributo formulario.deleted se establece como True .
Puedes modificar el controlador del ejemplo anterior para que cuando se pasa un nmero
entero como argumento adicional la ruta del URL, como en el siguiente ejemplo:
/prueba/default/mostrar_formulario/2

y si existe un registro en con el id correspondiente, el SQLFORM genera un formulario de


insercin y eliminacin para el registro:
def mostrar_formulario():
registro = db.persona(request.args(0)) or redirect(URL('index'))

formulario = SQLFORM(db.persona, registro)


if formulario.process().accepted:
response.flash = 'formulario aceptado'
elif formulario.errors:
response.flash = 'el formulario tiene errores'
return dict(formulario=formulario)

La lnea 2 encuentra el registro y la lnea 3 crea el formulario de eliminacin o modificacin. La


lnea 4 realiza la validacin y procesamiento del formulario adecuados.

Un formulario de modificacin es muy similar a uno para crear un registro con la excepcin
de que se carga con los datos actuales del registro y crea vistas previas de imgenes. Por
defecto se establece la opcin deletable = True ; esto significa que el formulario de
modificacin mostrar por defecto una opcin de eliminacin.
Los formularios de edicin tambin pueden contener campos de ingreso de datos con
valores name="id" que se usa para identificar el registro. Este id tambin se almacena del
lado del servidor para mayor seguridad y, si el visitante intenta realizar modificaciones no
autorizadas del valor de ese campo, la modificacin del registro no se realiza y web2py genera
el error SyntaxError, "el usuario est intentando modificar el formulario".
Cuando un campo de una tabla Field se marca con writable=False , el campo no se mostrar
en formularios de creacin, y se mostrar como slo lectura en formularios de modificacin.
Cuando un campo de una tabla se marca como writable=False y readable=False , entonces
no se mostrar en ningn formulario, incluyendo los formularios de modificacin.
Los formularios creados con
formulario = SQLFORM(...,ignore_rw=True)

omiten los atributos readable y writable y siempre muestran los campos. Los formularios
en appadmin tambin omiten estos parmetros por defecto.
Los formularios creados con
formulario = SQLFORM(tabla,id_registro, readonly=True)

siempre muestran todos los campos en modo slo lectura, y no se pueden enviar y procesar.
SQLFORM

como HTML

Hay ocasiones en las que quieres usar SQLFORM para aprovechar su mecanismo para crear
formularios y procesarlos, pero requieres de un nivel de personalizacin del formulario en
HTML que no puedes lograr con los parmetros de un objeto SQLFORM , de forma que tienes
que crear el formulario usando HTML.

Lo que debes hacer es editar previamente el controlador y agregar una nueva accin:
def mostrar_formulario_manual():
formulario = SQLFORM(db.persona)
if formulario.process(session=None, formname='prueba').accepted:
response.flash = 'formulario aceptado'
elif form.errors:
response.flash = 'el formulario tiene errores'
else:
response.flash = 'por favor completa el formulario'
# Ten en cuenta que no se pasa una instancia del formulario a la vista
return dict()

e insertar en el formulario la vista asociada "default/mostrar_formulario_manual.html":


{{extend 'layout.html'}}
<form>
<ul>
<li>Tu nombre es <input name="nombre" /></li>
</ul>
<input type="submit" />
<input type="hidden" name="_formname" value="prueba" />
</form>

Observa que la accin no devuelve el formulario porque no necesita pasarlo a la vista. La vista
contiene un formulario creado manualmente en HTML. El formulario contiene un campo oculto
que debe ser el mismo especificado como argumento de accepts en la accin. web2py usa el
nombre de formulario en caso de que haya mltiples formularios en la misma pgina, para
establecer cul se ha enviado. Si la pgina contiene un nico formulario, puedes
establecer formname=None y omitir el campo oculto en la vista.
formulario.accepts examinar response.vars en busca de datos que coincidan con campos

de la tabla de la base de datos db.persona . Estos campos se declaran en HTML con el


formato
<input name="el_nombre_del_campo_va_aqu" />

Ten en cuenta que en el ejemplo dado, las variables del formulario se pasarn en el URL como
argumentos. Si no quieres que esto ocurra, tendrs que especificar el protocolo POST .

Adems ten en cuenta que si especificas un campo upload, tendrs que configurar el
formulario para permitirlo. Aqu se muestran las dos opciones:
<form enctype="multipart/form-data" method="post">

SQLFORM

y subidas de archivos

Los campos de tipo "upload" son especiales. Se muestran como campos INPUT con el
atributo type="file" . A menos que se especifique lo contrario, el archivo a subir se transmite
con un stream utilizando un bufer, y se almacena dentro de la carpeta "uploads" de la
aplicacin, usando un nuevo nombre, seguro, asignado automticamente. El nombre del
archivo es entonces guardado en el campo de tipo upload.
Tomemos, como ejemplo, el siguiente modelo:
db.define_table('persona',
Field('nombre', requires=IS_NOT_EMPTY()),
Field('imagen', 'upload'))

Puedes usar la misma accin del controlador "mostrar_formulario" mostrado abajo.


Cuando insertas un nuevo registro, el formulario te permite buscar un archivo en tu sistema.
Elige, por ejemplo, una imagen en formato jpg. Este archivo se subir y almacenar en el
servidor como:
applications/prueba/uploads/persona.imagen.XXXXX.jpg

"XXXXXX" es un identificador aleatorio para el archivo asignado por web2py.

Observa que, por defecto, el nombre original del archivo de un campo upload se codifica
con Base16 y se usa para generar el nuevo nombre para el archivo. Este nombre se
recupera por defecto con la accin "download" y se usa para establecer el
encabezado content disposition de acuerdo con el tipo de datos del archivo original.
Slo se conserva la extensin del archivo. Esto se debe a un requerimiento de seguridad ya
que el archivo puede contener caracteres especiales que pueden habilitar al visitante para
perpetrar un ataque del tipo directory traversal y otras clases de operaciones maliciosas.
El nuevo archivo se almacena en formulario.vars.imagen .
Cuando se edita el registro usando el formulario de modificacin, sera mejor que se muestre
un link asociado al archivo subido al servidor, y web2py provee de una funcionalidad para
crearlo.
Si pasas un URL al constructor de SQLFORM a travs del argumento upload, web2py usa la
accin de ese URL para descargar el archivo. Tomemos como ejemplo las siguientes
acciones:
def mostrar_formulario():

registro = db.persona(request.args(0)) o redirect(URL('index'))


formulario = SQLFORM(db.persona, registro, deletable=True,
upload=URL('download'))
if formulario.process().accepted:
response.flash = 'formulario aceptado'
elif formulario.errors:
response.flash = 'el formulario tiene errores'
return dict(formulario=formulario)

def download():
return response.download(request, db)

Ahora, insertamos un nuevo registro en el URL:


http://127.0.0.1:8000/prueba/default/mostrar_formulario

Sube la imagen, enva el formulario, y luego edita el registro recientemente creado visitando:
http://127.0.0.1:8000/prueba/default/mostrar_formulario/3

(aqu asumimos que el ltimo registro tiene un id=3). El formulario mostrar una vista previa de
la imagen como se detalla abajo:

Este formulario, cuando es serializado, genera el siguiente cdigo HTML:


<td><label id="persona_imagen__label" for="persona_imagen">Imagen: </label></td>
<td><div><input type="file" id="persona_imagen" class="upload" name="imagen"
/>[<a href="/prueba/default/download/persona.imagen.0246683463831.jpg">archivo</a>|

<input type="checkbox" name="imagen__delete" />delete]</div></td><td></td></tr>


<tr id="delete_record__row"><td><label id="delete_record__label" for="delete_record"
>Marca aqu para eliminar:</label></td><td><input type="checkbox" id="delete_record"
class="delete" name="delete_this_record" /></td>

que contiene un link que permite la descarga de un archivo subido, y una opcin de
confirmacin para eliminar el archivo de la base de datos, y por lo tanto establecer el campo
"imagen" como NULL.
Por qu exponemos este mecanismo? Por qu querras escribir la funcin de descarga?
Porque puedes necesitar un mecanismo de control de acceso en esa funcin. Para un ejemplo
sobre este tema, consulta el Captulo 9.
Comnmente los archivos subidos se almacenan en un campo "app/uploads" pero es posible
especificar una ubicacin alternativa:
Field('imagen', 'upload', uploadfolder='...')

En la mayora de los sistemas operativos, el acceso al sistema de archivos puede tornarse


lento cuando existen muchos archivos en la misma carpeta. Si planeas subir ms de 1000
archivos puedes indicarle a web2py que organice los archivos en subcarpetas:
Field('imagen', 'upload', uploadseparate=True)

Almacenamiento del nombre original del archivo


web2py automticamente almacena el archivo original dentro del nuevo nombre de archivo
UUID y lo recupera cuando el archivo se descarga. Al descargarse, el nombre de archivo
original se almacena en el encabezado Content-Disposition de la respuesta HTTP. Esto se
hace en forma transparente, sin necesidad de programacin adicional.
En algunas ocasiones, podra interesarte almacenar el nombre de archivo original en un
campo de la base de datos. En ese caso, debes modificar el modelo y agregar un campo para
poder especificarlo:
db.define_table('persona',
Field('nombre', requires=IS_NOT_EMPTY()),
Field('nombre_archivo'),
Field('imagen', 'upload'))

luego necesitas modificar el controlador para que lo pueda almacenar:


def mostrar_formulario():
registro = db.persona(request.args(0)) or redirect(URL('index'))
url = URL('download')

formulario = SQLFORM(db.persona, registro, deletable=True,


upload=url, fields=['nombre', 'imagen'])
if request.vars.imagen!=None:
formulario.vars.nombre_archivo = request.vars.imagen.filename
if formulario.process().accepted:
response.flash = 'formulario aceptado'
elif formulario.errors:
response.flash = 'el formulario tiene errores'
return dict(formulario=formulario)

Observa

que

el SQLFORM no

muestra

el

campo

"nombre_archivo".

"mostrar_formulario"
pasa
el
nombre
del
archivo
en request.vars.imagen a formulario.vars.nombre_archivo para

de
que

los
sea

La

accin

parmetros
procesado

por accepts y almacenado en la base de datos. La funcin download, antes de servir los
archivos, comprueba en la base de datos el nombre de archivo original y lo usa en el
encabezado Content-Disposition.
autodelete

Cuando se elimina un registro con SQLFORM , este no elimina fsicamente los archivos/s
asociados al registro. La razn para esto es que web2py no sabe si el mismo archivo est
siendo usado por otra tabla o para otro propsito. Si consideras que es seguro el eliminar el
archivo en el sistema asociado al registro de la base de datos cuando se elimina el registro,
puedes hacer lo siguiente:
db.define_table('imagen',
Field('nombre', requires=IS_NOT_EMPTY()),
Field('origen','upload',autodelete=True))

El

atributo autodelete tiene

por

defecto

el

valor False .

Cuando

se

establece

como True web2py se asegura de que el archivo tambin se elimine cuando se borre el
registro.

Link a registros asociados


Ahora tomemos como ejemplo el caso de dos tablas asociadas por un campo de
tipo reference. Por ejemplo:
db.define_table('persona',
Field('nombre', requires=IS_NOT_EMPTY()))

db.define_table('perro',
Field('propietario', 'reference persona'),
Field('nombre', requires=IS_NOT_EMPTY()))
db.perro.propietario.requires = IS_IN_DB(db,db.person.id,'%(nombre)s')

Una persona puede tener perros, y cada perro pertenece a un propietario, que es una
persona. El propietario del perro debe estar asociado a un db.persona.id vlido con el
nombre '%(nombre)s' .
Usemos la interfaz appadmin de esta aplicacin para agregar algunas personas y sus perros.
Cuando se modifica una persona existente, el formulario de modificacin
en appadmin muestra un link a una pgina que lista los perros que pertenecen a esa persona.
Este
comportamiento
se
puede
reproducir
usando
el
argumento linkto del
objeto SQLFORM . linkto debe estar asociado al URL de una nueva accin que recibe una
cadena de consulta o query string desde el SQLFORM y lista los registros correspondientes.
He aqu un ejemplo:
def mostrar_formulario():
registro = db.persona(request.args(0)) or redirect(URL('index'))
url = URL('download')
link = URL('listar_registros', args='db')
formulario = SQLFORM(db.persona, registro, deletable=True,
upload=url, linkto=link)
if formulario.process().accepted:
response.flash = 'formulario aceptado'
elif formulario.errors:
response.flash = 'el formulario tiene errores'
return dict(formulario=formulario)

Esta es la pgina:

Hay un link llamado "perro.propietario". El nombre de este link se puede cambiar a travs del
argumento labels del objeto SQLFORM , por ejemplo:
labels = {'perro.propietario':" El perro de esta persona"}

Si haces clic en el link se abre la siguiente direccin:


/prueba/default/listar_registros/perro?query=db.perro.propietario%3D%3D5

"listar_registros" es la accin especificada, con el nombre de la tabla de referencia


en request.args(0) y la cadena de la consulta SQL almacenada en request.vars.query .

La cadena con la consulta en el URL contiene el valor "perro.propietario=5" convenientemente


codificado como texto url-encoded(web2py lo decodifica en forma automtica cuando se
analiza el URL).
Puedes implementar fcilmente una simple accin asociada a un listado amplio de registros de
esta forma:
def listar_registros():
REGEX = re.compile('^(\w+).(\w+).(\w+)\=\=(\d+)$')
match = REGEX.match(request.vars.query)
if not match:
redirect(URL('error'))
tabla, campo, id = match.group(2), match.group(3), match.group(4)
records = db(db[tabla][campo]==id).select()
return dict(registros=registros)

con la siguiente vista asociada "default/listar_registros.html":


{{extend 'layout.html'}}
{{=records}}

Cuando se devuelve un conjunto de registros con un comando select y se serializa en una


vista, primero se lo convierte en un objeto SQLTABLE (no es lo mismo que Table) y luego se
serializa en una tabla HTML, donde cada campo corresponde a una columna de la tabla.

Precompletando el formulario
Siempre es posible precompletar el formulario usando la sintaxis:
formulario.vars.nombre = 'valorcampo'

Este tipo de instrucciones deben ir insertadas a continuacin de la declaracin del formulario y


antes de que se acepten los datos ingresados, incluso si el campo (para este ejemplo
"nombre") no se debe visualizar en el formulario.

Agregando elementos adicionales al

SQLFORM

A veces puedes necesitar agregar un elemento adicional a tu formulario luego de haberlo


creado. Por ejemplo, puedes necesitar agregar una opcin checkbox de confirmacin para que
el usuario acepte las condiciones de uso del sitio web:
formulario = SQLFORM(db.tutabla)
mi_elemento_adicional = TR(LABEL('Estoy de acuerdo con el reglamento y las condiciones de uso
del sitio'),

INPUT(_name='deacuerdo',value=True,_type='checkbox'))

formulario[0].insert(-1, mi_elemento_adicional)

La variable mi_elemento_extra se debera adaptar al estilo del formulario o formstyle. En este


ejemplo, se supone el uso de formstyle='table3cols' .
Una vez enviado el formulario, formulario.vars.deacuerdo contendr el estado de la opcin,
que se puede usar, por ejemplo, en una funcin onvalidation .
SQLFORM

sin E/S de la base de datos

En algunas ocasiones, puedes necesitar generar un formulario a partir de una tabla de la base
de datos con SQLFORM y luego validarlo, como es usual, pero no quieres que se realicen
automticamente las operaciones INSERT/UPDATE/DELETE de la base de datos. Esto se da,
por ejemplo, cuando uno de los campos debe ser calculado a partir del valor de otros campos
de ingreso de datos. Tambin puede ser necesario cuando debemos realizar validaciones
previas adicionales de los datos ingresados que no son posibles por medio de los validadores
estndar.
Esto puede hacerse simplemente separando el siguiente cdigo:
formulario = SQLFORM(db.persona)
if formulario.process().accepted:
response.flash = 'registro insertado'

en:
formulario = SQLFORM(db.persona)
if formulario.validate():
### manejo especial de las subidas de archivos
formulario.vars.id = db.persona.insert(**dict(formulario.vars))
response.flash = 'registro insertado'

Lo mismo puede hacerse por medio formularios de edicin o eliminacin separando:


formulario = SQLFORM(db.persona, registro)
if formulario.process().accepted:
response.flash = 'registro actualizado'

en:
formulario = SQLFORM(db.persona, registro)
if formulario.validate():
if formulario.deleted:

db(db.persona.id==registro.id).delete()
else:
registro.update_record(**dict(formulario.vars))
response.flash = 'registro actualizado'

En el caso de la tabla que incluye un campo de tipo "upload", por ejemplo, "nombredelcampo",
tanto process(dbio=False) como validate() se encargan del almacenamiento del archivo
subido como si se hubiese establecido process(dbio=True) , que es el comportamiento por
defecto.
El nombre asignado por web2py al archivo subido se puede recuperar con:
formulario.vars.nombredelcampo

Otros tipos de formulario


SQLFORM.factory

Hay casos en los que quieres generar formularios como si tuvieran una tabla de la base de
datos asociada pero no quieres modificar una tabla determinada. Simplemente quieres
aprovechar la funcionalidad de SQLFORM para generar formularios vistosos y aptos para
trabajo con CSS y quizs subir archivos y realizar cambios de nombre.
Esto se puede hacer a travs de un factory para formularios. Este es un ejemplo donde
generas el formulario, realizas la validacin, subes un archivo y almacenas todos los datos
en session :
def formulario_con_factory():
formulario = SQLFORM.factory(
Field('tu_nombre', requires=IS_NOT_EMPTY()),
Field('tu_imagen', 'upload'))
if formulario.process().accepted:
response.flash = 'formulario aceptado'
session.your_name = formulario.vars.tu_nombre
session.your_image = formulario.vars.tu_imagen
elif formulario.errors:
response.flash = 'el formulario tiene errores'
return dict(formulario=formulario)

El objeto Field usado para el constructor de SQLFORM.factory() est completamente


documentado en el captulo de DAL. Una forma de creacin del formulario SQLFORM.factory()
al vuelo puede ser
campos = []
campos.append(Field(...))
formulario=SQLFORM.factory(*campos)

Esta es la vista "default/formulario_con_factory.html":


{{extend 'layout.html'}}
{{=formulario}}

Debes usar un subguin en lugar de un espacio para etiquetas, o pasar en forma explcita un
diccionario de etiquetas labels a factory , como lo haras para el caso de SQLFORM . Por
defecto, SQLFORM.factory crea el formulario usando los atributos "id" de html como si el
formulario se hubiera creado a partir de una tabla llamada "no_table". Para cambiar este
nombre ficticio de tabla, usa el parmetro table_name de factory:
formulario = SQLFORM.factory(...,table_name='otro_nombre_ficticio')

Es conveniente cambiar el valor de table_name cuando quieres colocar dos formularios


factory en la misma tabla. De esta forma evitars conflictos con el CSS.

Subiendo archivos con SQLFORM.factory

Un solo formulario para mltiples tablas


A menudo ocurre que tienes dos tablas (por ejemplo 'cliente' y 'direccion') que estn asociadas
por un campo de tipo reference y quieres crear un nico formulario que permita ingresar
informacin sobre el cliente y su direccin por defecto. Esto es lo que debes hacer:
modelo:
db.define_table('cliente',
Field('nombre'))
db.define_table('direccion',
Field('cliente','reference cliente',
writable=False,readable=False),
Field('calle'),Field('ciudad'))

controlador:
def registrarse():

formulario=SQLFORM.factory(db.cliente,db.direccion)
if formulario.process().accepted:
id = db.cliente.insert(**db.cliente._filter_fields(formulario.vars))
formulario.vars.cliente=id
id = db.direccioin.insert(**db.direccion._filter_fields(formulario.vars))
response.flash='Gracias por completar el formulario'
return dict(formulario=formulario)

Observa el SQLFORM.factory (este crea UN formulario usando los campos de ambas tablas,
heredando adems sus validadores). Cuando el formulario se acepta hace dos inserciones en
la base de datos, algunos de los datos van a una tabla y los dems a la otra.

Esto nicamente funciona cuando no existen campos de distintas tablas cuyos nombres
coinciden.

Formularios de confirmacin
Muchas veces debes crear un formulario con una opcin de confirmacin. El formulario
debera aceptarse slo si esa opcin se ha aceptado. El formulario puede tener opciones
adicionales que enlacen a otras pginas web. web2py provee de una forma simple de hacerlo:
formulario = FORM.confirm('Ests seguro?')
if formulario.accepted: hacer_algo_mas()

Observa que el formulario de confirmacin no requiere y no debe llamar


a .accepts o .process porque esto se hace internamente. Puedes agregar botones con link al
formulario de confirmacin utilizando un diccionario con la forma {'valor': 'link'} :
formulario = FORM.confirm('Ests seguro?',{'Volver':URL('otra_pagina')})
if formulario.accepted: hacer_algo_mas()

Formulario para editar un diccionario


Supongamos un sistema que almacena opciones de configuracin en un diccionario,
configuracion = dict(color='negro', idioma='Espaol')

y necesitas un formulario para permitir al visitante que modifique ese diccionario: Esto se
puede hacer de este modo:
formulario = SQLFORM.dictform(configuracion)
if formulario.process().accepted: configuracion.update(formulario.vars)

El formulario mostrar un campo de ingreso de datos INPUT para cada tem del diccionario.
Usar las claves del diccionario como nombres de los campos y etiquetas y los valores
asociados por defecto para obtener los tipos de datos (cadena, entero, coma flotante, fecha y
hora, booleano)
Esto funciona muy bien pero ests obligado a programar la parte que hace que los datos de
configuracin
ingresados
sean
permanentes.
Por
ejemplo
puedes
necesitar
almacenar configuracion en una sesin.
session.configuracion or dict(color='negro', idioma='Espaol')
formulario = SQLFORM.dictform(session.configuracion)
if formulario.process().accepted:
session.configuracion.update(formulario.vars)

CRUD
Una de las adiciones recientes a web2py es la API de ABM para Crear/Leer/Modificar/Borrar
CRUD, que funciona sobre SQLFORM. CRUD crea un SQLFORM, pero simplifica el cdigo
porque incorpora la creacin del formulario, el procesamiento de los datos ingresados, las
notificaciones y la redireccin, todo en una sola funcin.
Lo primero que hay que destacar es que CRUD difiere del resto de las API de web2py que
hemos visto hasta aqu porque en un principio no se expone. Se debe importar en forma
explcita. Adems debe estar asociado a una base de datos especfica. Por ejemplo:
from gluon.tools import Crud
crud = Crud(db)

El objeto crud definido arriba provee de la siguiente API:


o

crud.tables() devuelve una lista de tablas definidas en la base de datos.

crud.create(db.nombredelatabla) devuelve un formulario de creacin para la tabla

nombredetabla.
o

crud.read(db.nombredelatabla, id) devuelve un formulario de solo lectura para el

registro id en nombredelatabla.
o

crud.update(db.nombredelatabla, id) devuelve un formulario de modificacin para el

registro id en nombredelatabla.
o

crud.delete(db.nombredelatabla, id) elimina el registro.

crud.select(db.nombredelatabla, consulta) devuelve una lista de registros recuperados

de la tabla.
o

crud.search(db.nombredelatabla) devuelve una tupla (formulario, registros) donde

formulario es un formulario de bsqueda y registros es una lista de registros segn los


datos enviados a travs del formulario.
o

crud() devuelve

uno

de

los

formularios

anteriores

segn

se

especifique

en request.args() .
Por ejemplo, la siguiente accin:
def data(): return dict(formulario=crud())

expondr los siguientes URL:


http://.../[app]/[controlador]/data/tables
http://.../[app]/[controlador]/data/create/[nombredelatabla]
http://.../[app]/[controlador]/data/read/[nombredelatabla]/[id]
http://.../[app]/[controlador]/data/update/[nombredelatabla]/[id]
http://.../[app]/[controlador]/data/delete/[nombredelatabla]/[id]
http://.../[app]/[controlador]/data/select/[nombredelatabla]
http://.../[app]/[controlador]/data/search/[nombredelatabla]

Por otro lado, la siguiente accin:


def crear_nombredelatabla():
return dict(formulario=crud.create(db.nombredelatabla))

solo expondr la funcionalidad para crear registros


http://.../[app]/[controlador]/crear_nombredelatabla

Mientras que la siguiente accin:


def actualizar_nombredelatabla():
return dict(formulario=crud.update(db.nombredelatabla, request.args(0)))

expondr nicamente la funcionalidad para modificar registros


http://.../[app]/[controlador]/modificar_nombredelatabla/[id]

y as sucesivamente.
El comportamiento de CRUD se puede personalizar de dos formas distintas: configurando un
atributo del objeto crud o pasando parmetros adicionales a sus distintos mtodos.

Configuracin
He aqu una lista completa de los atributos implementados en CRUD, sus valores por defecto,
y su significado:
Para el control de autenticacin en todos los formularios crud:
crud.settings.auth = auth

Su uso se explica en el captulo 9.


Para especificar el controlador que define la funcin data que devuelve el objeto crud
crud.settings.controller = 'default'

Para especificar el URL al cual redirigir luego de crear exitosamente un registro con "create":
crud.settings.create_next = URL('index')

Para especificar el URL al cual redirigir luego de modificar exitosamente un registro con
"update":
crud.settings.update_next = URL('index')

Para especificar el URL al cual redirigir luego de eliminar exitosamente un registro con
"delete":
crud.settings.delete_next = URL('index')

Para especificar el URL que se usar como link para los archivos subidos:
crud.settings.download_url = URL('download')

Para especificar las funciones adicionales a ejecutarse despus de la validacin estndar para
los formularios crud.create :
crud.settings.create_onvalidation = StorageList()
StorageList es lo mismo que el objeto Storage , ambos se definen en "gluon/storage.py", la

diferencia es que el primero tiene el valor [] por defecto en lugar de None . Esto permite la
siguiente sintaxis:
crud.settings.create_onvalidation.minombredetabla.append(lambda formulario:....)

Para especificar funciones adicionales a ejecutarse luego de la validacin estndar para los
formularios crud.update :
crud.settings.update_onvalidation = StorageList()

Para especificar funciones adicionales a ejecutarse cuando finalice un formulario crud.create :


crud.settings.create_onaccept = StorageList()

Para especificar funciones


formulario crud.update :

adicionales

ejecutarse

luego

de

finalizar

un

crud.settings.update_onaccept = StorageList()

Para
especificar
funciones
adicionales
a
formulario crud.update cuando se elimina el registro:

ejecutarse

al

finalizar

un

crud.settings.update_ondelete = StorageList()

Para especificar funciones adicionales a ejecutarse cuando finalice un formulario crud.delete :


crud.settings.delete_onaccept = StorageList()

Para determinar si los formularios "update" deben tener un botn para eliminar el registro:
crud.settings.update_deletable = True

Para establecer si los formularios "update" deberan mostrar el id del registro modificado:
crud.settings.showid = False

Para indicar si los formularios deberan mantener los valores insertados previamente o tomar
los valores por defecto al procesarse exitosamente un formulario:
crud.settings.keepvalues = False

Crud siempre detecta si un registro que est siendo editado ha sido modificado por un tercero
durante el proceso de mostrar el formulario y su validacin al ser enviado. Este
comportamiento es equivalente a
formulario.process(detect_record_change=True)

y se establece en:
crud.settings.detect_record_change = True

y se puede modificar o deshabilitar estableciendo la variable como False .


Puedes modificar el estilo del formulario por defecto con
crud.settings.formstyle = 'table3cols' or 'table2cols' or 'divs' or 'ul'

Puedes establecer un separador para todos los formularios:


crud.settings.label_separator = ':'

Puedes agregar captcha a los formularios, usando la misma convencin explicada para auth,
con:
crud.settings.create_captcha = None
crud.settings.update_captcha = None
crud.settings.captcha = None

Mensajes
Esta es la lista de mensajes personalizables:
crud.messages.submit_button = 'Enviar'

establece el texto del botn "submit" para los formularios de creacin y modificacin.
crud.messages.delete_label = 'Marca para eliminar:'

etablece la etiqueta del botn "delete" en los formularios de modificacin.


crud.messages.record_created = 'Registro creado'

establece el mensaje emergente para la creacin exitosa de registros.


crud.messages.record_updated = 'Registro actualizado'

establece el mensaje emergente para la concrecin de una actualizacin de registro.


crud.messages.record_deleted = 'Registro eliminado'

establece el mensaje emergente para la eliminacin satisfactoria de un registro.


crud.messages.update_log = 'Registro %(id)s actualizado'

establece el mensaje a registrar en el log para la actualizacin de un registro.


crud.messages.create_log = 'Registro %(id)s creado'

establece el mensaje a registrar en el log cuando se crea un registro.


crud.messages.read_log = 'Registro %(id)s ledo'

establece el mensaje a registrar en el log cuando se accede a un registro normalmente.


crud.messages.delete_log = 'Registro %(id)s borrado'

establece el mensaje a registrar en el log cuando se elimina con xito un registro.

Observa que los crud.messages pertenecen a la clase gluon.storage.Message que es


similar a gluon.storage.Storage . La diferencia es que el primero traduce automticamente
sus valores, sin necesidad de usar el operador T .
Los mensajes del log se usan si y solo si CRUD est conectado a Auth como se detalla en el
Captulo 9. Los eventos se registran en la tabla "auth_events".

Mtodos
El comportamiento de CRUD tambin se puede personalizar en funcin de cada llamada.
Estas son las listas de argumentos soportadas:
crud.tables()
crud.create(tabla, next, onvalidate, onaccept, log, message)

crud.read(tabla, registro)
crud.update(tabla, registro, next, onvalidate, onaccept, ondelete, log, message, deletable)
crud.delete(table, id_registro, next, message)
crud.select(tabla, query, fields, orderby, limitby, headers, **attr)
crud.search(tabla, query, queries, query_labels, fields, field_labels, zero, showall, chkall)

tabla es una tabla de DAL o nombre de tabla que debe usar el mtodo.

registro e id_registro son los id del registro que debe utilizar el mtodo.

next es el URL de redireccin al finalizar el procesamiento del formulario. Si el URL

contiene la cadena "[id]" esta ser reemplazada por el id del registro actual procesado
por el formulario.
o

onvalidate tiene la misma funcionalidad que SQLFORM(..., onvalidation)

onaccept es la funcin a llamar luego de que el formulario enviado sea aceptado y se

procesen los datos, pero antes de la redireccin.


o

log es el mensaje a registrar en el log. Los log de mensajes en CRUD examinan

variables del diccionario formulario.vars , por ejemplo "%(id)s".


o

message es el mensaje emergente que se muestra al aceptarse el formulario.

ondelete se llama en lugar de onaccept cuando un registro es borrado a travs de un

formulario "update".
o

deletable determina si el formulario "update" debe tener una opcin "eliminar".

query es la consulta a usar para recuperar los registros.

fields es una lista de campos a seleccionar.

orderby determina el orden en el cual los registros se deberan recuperar (consulta el

Captulo 6 para ms informacin).


o

limitby determina el rango de los registros seleccionados que deberan mostrarse

(para ms detalles consulta el Captulo 6).

headers es un diccionario con los nombres de los encabezados de la tabla.

queries es una lista como ['equals', 'not equal', 'contains'] que contiene una serie de

mtodos permitidos en el formulario de bsqueda.


o

query_labels es un diccionario como query_labels=dict(equals='Igual') para asignar

nombres a los distintos mtodos de bsqueda.


o

campos es una lista de campos que se deben listar en el widget de bsqueda.

field_labels es un diccionario que asocia los nombres de los campos con etiquetas.

zero es "elige uno" por defecto. Es usado como opcin predeterminada para el men

desplegable en el widget de bsqueda.


o

showall configralo como True si quieres que muestren los registros de la consulta la

primera vez que se llama a la accin (disponible desde 1.98.2).


o

chkall configralo como True si quieres que todas las opciones checkbox del

formulario de bsqueda estn habilitadas por defecto (disponible desde 1.98.2).


Aqu se muestra un ejemplo de uso en una sola funcin:
## se asume una tabla definida con db.define_table('persona', Field('nombre'))
def gente():
formulario = crud.create(db.persona, next=URL('index'),
message=T("registro creado"))
personas = crud.select(db.persona, fields=['nombre'],
headers={'persona.nombre': 'Nombre'})
return dict(formulario=formulario, personas=personas)

He aqu otra funcin bastante genrica del controlador que te permite buscar, crear y editar
cualquier registro de cualquier tabla donde el nombre de tabla es un parmetro pasado como
request.args(0):
def administrar():
tabla=db[request.args(0)]
formulario = crud.update(tabla,request.args(1))
tabla.id.represent = lambda id, registro:
id)))

A('Editar:', id, _href=URL(args=(request.args(0),

busqueda, registros = crud.search(tabla)


return dict(formulario=formulario, busqueda=busqueda, registros=registros)

Observa

que

la

lnea tabla.id.represent=... le

indica

web2py

como

cambiar

la

representacin del campo id y en cambio mostrar un link a la pgina en s, y pasando a su vez


el id a request.args(1), que convierte la pgina de creacin en una pgina de modificacin.

Control de versiones de registros


Tanto SQLFORM como CRUD proveen de una utilidad para hacer record versioning, es decir,
para administrar versiones de registros de la base de datos:
Si tienes una tabla (db.mitabla) que requiere un registro completo de sus versiones puedes
simplemente hacer:
formulario = SQLFORM(db.mitabla, miregistro).process(onsuccess=auth.archive)
formulario = crud.update(db.mitabla, miregistro, onaccept=auth.archive)
auth.archive define una nueva tabla llamada db.mitabla_archive (el nombre se construye

con el nombre de la tabla a la que est asociada) y al actualizase un registro, se almacena


una copia (con los datos previos a la actualizacin) en la nueva tabla archive, incluyendo una
referencia al registro actual.
Estos registros se actualizan constantemente (conservando nicamente el ltimo estado), y
por lo tanto, tambin se mantienen actualizadas las referencias.
Todo esto es hecho en forma transparente. Si por ejemplo quisieras acceder a la tabla archive,
deberas definirla en el modelo:
db.define_table('mitabla_archive',
Field('current_record', 'reference mitabla'),
db.mitabla)

Observa que la tabla extiende db.mitabla (incluyendo a todos sus campos), y agrega una
referencia current_record al registro actual.
auth.archive no registra la fecha y hora del registro almacenado a menos que tu tabla original

tenga campos de fecha y hora, por ejemplo:


db.define_table('mitabla',
Field('creado_el', 'datetime',
default=request.now, update=request.now, writable=False),
Field('creado_por', 'reference auth_user',
default=auth.user_id, update=auth.user_id, writable=False),

No hay nada de especial en estos campos y puedes asignarles el nombre que quieras. Estos
campos se completan antes de que el registro se archive y se archivan con cada copia del
registro. El nombre de la tabla archive y/o el campo de referencia se pueden cambiar de esta
forma:
db.define_table('mihistoria',
Field('parent_record', 'reference mytable'),
db.mytable)
## ...
formulario = SQLFORM(db.mitabla, miregistro)
formulario.process(onsuccess = lambda formulario:auth.archive(formulario,
archive_table=db.mihistoria,
current_record='parent_record'))

Formularios personalizados
Si se crea un formulario con SQLFORM, SQLFORM.factory o CRUD, hay mltiples formas de
embeberlo en una vista permitiendo mltiples grados de personalizacin. Considera por
ejemplo el siguiente modelo:
db.define_table('imagen',
Field('nombre', requires=IS_NOT_EMPTY()),
Field('datos', 'upload'))

y una accin para subida de archivos


def subir_imagen():
return dict(formulario=SQLFORM(db.imagen).process())

La forma ms sencilla de embeber el formulario en la vista para subir_imagen es


{{=formulario}}

Esto produce un diseo estndar de tabla. Si quisieras usar otro diseo, podras separar el
formulario en componentes
{{=formulario.custom.begin}}
Nombre de la imagen: <div>{{=formulario.custom.widget.nombre}}</div>
Archivo de la imagen: <div>{{=formulario.custom.widget.datos}}</div>
Clic aqu para subir: {{=formulario.custom.submit}}

{{=formulario.custom.end}}

donde formulario.custom.widget[nombredelcampo] se serializa en el widget correspondiente


para el campo. Si el formulario se procesa y este contiene errores, los errores se agregarn
debajo de los widget, como es usual.
El formulraio del ejemplo anterior se muestra en la imagen de abajo.

Podamos obtener un efecto similar sin el uso de un formulario personalizado, usando:


SQLFORM(..., formstyle='table2cols')

o en caso de formularios CRUD con el siguiente parmetro:


crud.settings.formstyle='table2cols'

Otros formstyle posibles son "table3cols" (el estilo por defecto), "divs" y "ul".
Si no deseas usar widget serializados por web2py, los puedes reemplazar por HTML. Hay
algunas variables que puedes usar para ese propsito:
o

form.custom.label[nombredelcampo] contiene la etiqueta para el campo.

form.custom.comment[nombredelcampo] contiene el comentario para el campo.

form.custom.dspval[nombredelcampo] valor de visualizacin del campo en funcin del

tipo y estilo de formulario.


o

form.custom.inpval[nombredelcampo] valores para el campo que se usarn en el

procesamiento, en funcin del tipo y estilo del formulario.


Si tu formulario tiene la opcin deleteable=True tambin deberas agregar
{{=form.custom.delete}}

para mostrar la opcin checkbox de eliminar.


Es importante que sigas las convenciones descriptas a continuacin.

Convenciones para CSS


Las etiquetas en formularios que crean SQLFORM, SQLFORM.factory y CRUD siguen una
convencin para el uso de nombres en CSS estricta que puede ser usada para realizar
personalizaciones posteriores a los formularios.
Dada una tabla "mitabla", y un campo "micampo" de tipo "string", estos son convertidos por
defecto por un
SQLFORM.widgets.string.widget

que se ve de la siguiente forma:


<input type="text" name="micampo" id="mitabla_micampo"
class="string" />

Observa que:
o

la clase de la etiqueta del campo para ingreso de datos INPUT equivale al tipo del
campo. Esto es muy importante para que el cdigo jQuery en "web2py_ajax.html"
funcione. Ese cdigo se asegura de que se ingresen nicamente valores numricos en
campos "integer" o "double" y de que los campos "date" y "datetime" muestren un
calendario emergente.

El id es el nombre de la clase ms el nombre del campo, unidos por un subguin. Esto


te permite hacer referencias especficas al campo, por ejemplo, por medio
de jQuery('#mytable_myfield') y manipular la hoja de estilo del campo o asociar
acciones a los eventos del campo (focus, blur, keyup, etc.).

el nombre es, como es de esperarse, el nombre del campo.

Ocultar errores
En ocasiones, puedes necesitar deshabilitar los informes automticos de errores y mostrar los
errores de formularios en otras ubicaciones que no sean las establecidas por defecto. Esto se
puede hacer fcilmente.
o

Para el caso de los FORM y SQLFORM, debes especificar hideerror=True en el


mtodo accepts .

En el caso de CRUD, establece crud.settings.hideerror=True

Adems podras modificar las vistas para mostrar los errores (ya que ahora no se mostrarn
automticamente).
Este es un ejemplo para mostrar los errores sobre el formulario y no dentro de l.
{{if formulario.errors:}}
Tu envo de formulario contiene los siguientes errores:
<ul>
{{for nombredelcampo in formulario.errors:}}
<li>{{=nombredelcampo}} error: {{=formulario.errors[nombredelcampo]}}</li>
{{pass}}
</ul>
{{formulario.errors.clear()}}
{{pass}}
{{=formulario}}

Los errores se mostrarn como en la imagen de abajo.

Este mecanismo tambin funciona con formularios personalizados.

Validadores
Los validadores son clases que se usan para validar campos de ingreso de datos (incluyendo
los formularios generados a partir de tablas de la base de datos).

Aqu se muestra un ejemplo de uso de un validador en un FORM :


INPUT(_name='a', requires=IS_INT_IN_RANGE(0, 10))

Este ejemplo muestra como especificar la validacin en un campo de una tabla:


db.define_table('persona', Field('nombre'))
db.persona.nombre.requires = IS_NOT_EMPTY()

Los validadores siempre se asignan usando el atributo requires de un campo. Un campo


puede tener uno o mltiples validadores, los validadores mltiples deben incluirse en una lista:
db.persona.nombre.requires = [IS_NOT_EMPTY(),
IS_NOT_IN_DB(db, 'persona.nombre')]

Normalmente
los
validadores
son
llamados
automticamente
por
la
funcin accepts y process de un FORM u otro objeto ayudante de HTML que contenga un
formulario. Los validadores se llaman en el orden en el que fueron listados.
Uno puede adems llamar explcitamente a los validadores para un campo:
db.persona.nombre.validate(valor)

que devuelve una tupla (valor, error) y error es None cuando el valor pasa la validacin.
Los validadores incorporados tienen constructores que toman un argumento opcional:
IS_NOT_EMPTY(error_message='no puede estar vaco')
error_message te permite sobrescribir el mensaje de error por defecto de cualquier validador.

Aqu hay un ejemplo de validador aplicado a una tabla de la base de datos:


db.persona.nombre.requires = IS_NOT_EMPTY(error_message='Completa este campo!')

donde hemos usado el operador de traduccin T para permitir mltiples traducciones del
contenido o internationalization. Observa que los mensajes de error por defecto no se
traducen.
Ten en cuenta que los nicos validadores que se pueden usar con los tipos list: son:
o

IS_IN_DB(..., multiple=True)

IS_IN_SET(..., multiple=True)

IS_NOT_EMPTY()

IS_LIST_OF(...)

El ltimo se puede usar para aplicar cada validador a los tems de la lista individualmente.

Validadores
IS_ALPHANUMERIC

Este validador comprueba que el valor del campo contenga solo caracteres en los rangos a-z,
A-Z, o 0-9.
requires = IS_ALPHANUMERIC(error_message='Debe ser alfanumrico!')

IS_DATE

Este validador comprueba que el valor del campo contenga una fecha vlida en el formato
especificado. Es una buena prctica el especificar el formato usando el operador de
traduccin, para contemplar distintos formatos segn el uso local.
requires = IS_DATE(format=T('%Y-%m-%d'),
error_message='Debe ser YYYY-MM-DD!')

Para una descripcin completa de los parmetros % consulta la seccin del validador
IS_DATETIME.
IS_DATE_IN_RANGE

Funciona en forma muy similar al validador anterior, pero permite especificar un rango:
requires = IS_DATE_IN_RANGE(format=T('%Y-%m-%d'),
minimum=datetime.date(2008,1,1),
maximum=datetime.date(2009,12,31),
error_message='Debe ser YYYY-MM-DD!')

Para una descripcin completa de los parmetros % consulta la seccin del validador
IS_DATETIME.
IS_DATETIME

Este validador comprueba que el valor del campo contenga fecha y hora validas en el formato
especificado. Es buena prctica el especificar el formato usando el operador de traduccin,
para contemplar los distintos formatos segn el uso local.
requires = IS_DATETIME(format=T('%Y-%m-%d %H:%M:%S'),
error_message='Debe ser YYYY-MM-DD HH:MM:SS!')

Los siguientes smbolos se pueden usar para la cadena del argumento format (se muestra el
smbolo y una cadena de ejemplo):
%Y '1963'
%y '63'

%d '28'
%m '08'
%b 'Aug'
%b 'August'
%H '14'
%I '02'
%p 'PM'
%M '30'
%S '59'

IS_DATETIME_IN_RANGE

Funciona de una forma muy similar al validador previo, pero permite especificar un rango:
requires = IS_DATETIME_IN_RANGE(format=T('%Y-%m-%d %H:%M:%S'),
minimum=datetime.datetime(2008,1,1,10,30),
maximum=datetime.datetime(2009,12,31,11,45),
error_message='Debe ser YYYY-MM-DD HH:MM::SS!')

Para una descripcin completa del parmetro % consulta la seccin del validador
IS_DATETIME.
IS_DECIMAL_IN_RANGE
INPUT(_type='text', _name='name', requires=IS_DECIMAL_IN_RANGE(0, 10, dot="."))

Convierte los datos ingresados en objetos Decimal de Python o genera un error si el valor
decimal no est comprendido por los lmites, incluyendo el mnimo y el mximo. La
comparacin se hace por medio de algoritmos implementados en Decimal.
Los lmites mximo y mnimo pueden ser None, lo que implica que no hay lmite superior o
inferior, respectivamente.
El argumento dot , es opcional y te permite aplicar traduccin automtica al smbolo usado
para separar los decimales.
IS_EMAIL

Comprueba que el campo tenga el formato corriente para una direccin de correo electrnico.
No intenta verificar la autenticidad de la cuenta enviando un mensaje.
requires = IS_EMAIL(error_message='El mail no es vlido!')

IS_EQUAL_TO

Comprueba que el valor validado sea igual al valor especificado (que tambin puede ser una
variable):
requires = IS_EQUAL_TO(request.vars.password,
error_message='Las contraseas no coinciden')

IS_EXPR

Su primer argumento es una cadena que contiene una expresin lgica en funcin de una
variable. El campo valida si la expresin evala a True . Por ejemplo:
requires = IS_EXPR('int(value)%3==0',
error_message='No es divisible por 3')

Se debera comprobar primero que el valor sea un entero para que no se generen
excepciones.
requires = [IS_INT_IN_RANGE(0, 100), IS_EXPR('value%3==0')]

IS_FLOAT_IN_RANGE

Comprueba que el valor de un campo sea un nmero de coma flotante en el rango


especificado, 0 <= valor <= 100 para el caso del siguiente ejemplo:
requires = IS_FLOAT_IN_RANGE(0, 100, dot=".",
error_message='Demasiado pequeo o demasiado grande!')

El argumento dot es opcional y te permite contemplar la traduccin automatizada del smbolo


para separar los valores decimales.
IS_INT_IN_RANGE

Comprueba que el valor del campo sea un entero en el rango definido, 0 <= value <
100 para el caso del siguiente ejemplo:
requires = IS_INT_IN_RANGE(0, 100,
error_message='Demasiado pequeo o demasiado grande!')

IS_IN_SET

Comprueba que los valores del campo estn comprendidos en un conjunto:


requires = IS_IN_SET(['a', 'b', 'c'], zero=T('Elige uno'),
error_message='Debe ser a, b o c')

El argumento zero es opcional y determina el texto de la opcin seleccionada por defecto, pero
que no pertenece al conjunto de valores admitidos por el validador IS_IN_SET. Si no quieres
un texto por defecto, especifica zero=None .

La opcin zero se introdujo en la versin (1.67.1). No rompi la compatibilidad hacia atrs en


el sentido de que no est en conflicto con aplicaciones anteriores, pero s cambi su
comportamiento, ya que antes no exista esa opcin.
Los elementos del conjunto deben ser siempre cadenas a menos que el validador sea
precedido
por IS_INT_IN_RANGE (que
convierte
el
valor
en
un
entero)
o IS_FLOAT_IN_RANGE (que convierte el valor en nmero de coma flotante). Por ejemplo:
requires = [IS_INT_IN_RANGE(0, 8), IS_IN_SET([2, 3, 5, 7],
error_message='Debe ser un nmero primo menor a 10')]

Tambin puedes usar un diccionario o una lista de tuplas para hacer que el men desplegable
sea ms descriptivo:
#### Ejemplo con un diccionario:
requires = IS_IN_SET({'A':'Manzana','B':'Banana','C':'Cereza'}, zero=None)
#### Ejemplo con una lista de tuplas:
requires = IS_IN_SET([('A','Manzana'),('B','Banana'),('C','Cereza')])

IS_IN_SET

y selecciones mltiples

El validador IS_IN_SET tiene un atributo opcional multiple=False . Si se establece como True,


se pueden almacenar mltiples valores en un nico campo. El campo debera ser de
tipo list:integer o list:string . Las referencias mltiples se manejan automticamente en
formularios para crear y actualizar, pero son transparentes para DAL. Se aconseja
especialmente el uso del plugin de jQuery multiselect para mostrar campos mltiples.

Ten en cuenta que cuando se verifica multiple=True , IS_IN_SET aceptar el valor


especificado en zero o ms, es decir, aceptar el campo cuando no se haya seleccionado
nada. multiple tambin puede ser una tupla con el formato (a, b) , donde a y b son el
mnimo y el mximo (exclusive) nmero de tems que se pueden seleccionar
respectivamente.
IS_LENGTH

Comprueba que la longitud del valor de un campo se encuentre entre los lmites establecidos.
Funciona tanto para campos de texto como para archivos.
Sus argumentos son:
o

maxsize: la longitud o tamao mximos admitidos (por defecto es 255)

minsize: la longitud o tamao mnimo admitidos

Ejemplos:

Comprobar que la cadena de texto tiene una longitud menor a 33 caracteres:


INPUT(_type='text', _name='nombre', requires=IS_LENGTH(32))

Comprobar que una contrasea tiene ms de 5 caracteres:


INPUT(_type='password', _name='nombre', requires=IS_LENGTH(minsize=6))

Comprobar que un archivo subido pesa entre 1KB y 1MB:


INPUT(_type='file', _name='nombre', requires=IS_LENGTH(1048576, 1024))

Para todo tipo de campo excepto los de archivos, comprueba la longitud del valor. En el caso
de los archivos, el valor es de tipo cookie.FieldStorage , por lo que se valida, siguiendo el
comportamiento esperado normalmente, la longitud de los datos en el archivo.
IS_LIST_OF

Este no es exactamente un validador. Su funcionalidad consiste en permitir a los validadores


de campos que devuelvan valores mltiples. Se utiliza en esos casos especiales en los que el
formulario contiene mltiples campos con el mismo nombre o una caja de seleccin mltiple.
Su nico argumento es otro validador, y todo lo que hace es aplicar el otro validador a cada
elemento de la lista. Por ejemplo, la siguiente expresin comprueba que cada tem en una lista
sea un entero en un rango entre 0 y 10:
requires = IS_LIST_OF(IS_INT_IN_RANGE(0, 10))

Nunca devolver un error ni contiene mensajes de error. Es el validador anidado el que


controlar la generacin de errores.
IS_LOWER

Este validador nunca devuelve un error. Solo convierte el valor de entrada a minsculas.
requires = IS_LOWER()

IS_MATCH

Este validador compara el valor segn una expresin regular y devuelve un error cuando la
expresin no coincide. Aqu se muestra un ejemplo de uso del validador para comprobar un
cdigo postal de Estados Unidos:
requires = IS_MATCH('^\d{5}(-\d{4})?$',
error_message='No es un cdigo postal vlido')

Este ejemplo valida uan direccin IPv4 (nota: el validador IS_IPV4 es ms apropiado para este
propsito):
requires = IS_MATCH('^\d{1,3}(.\d{1,3}){3}$',
error_message='No es direccin IPv4')

Aqu se comprueba un nmero de telfono vlido para Estados Unidos:

requires = IS_MATCH('^1?((-)\d{3}-?|\(\d{3}\))\d{3}-?\d{4}$',
error_message='No es un nmero de telfono')

Para ms informacin sobre expresiones regulares en Python, puedes consultar la


documentacin oficial de Python.
IS_MATCH toma un argumento opcional strict que por defecto es False . Cuando se establece

como True slo compara el inicio de la cadena:


>>> IS_MATCH('a')('ba')
('ba', <lazyT 'Expresin invlida'>) # no aceptado
>>> IS_MATCH('a', strict=False)('ab')
('a', None)

# Aceptado!

IS_MATCH toma otro argumento opcional buscar que por defecto es False . Cuando se

establece

como True ,

usa

el

mtodo

de

expresin

regular search en

lugar

del

mtodo match para validar la cadena.


IS_MATCH('...', extract=True) filtra y extrae slo la primer seccin que encuentre cuyo valor

coincida en lugar de devolver el valor original.


IS_NOT_EMPTY

Este validador comprueba que el contenido del campo no sea una cadena vaca.
requires = IS_NOT_EMPTY(error_message='No puede estar vaco!')

IS_TIME

Este validador comprueba que el valor del campo contenga una hora vlida en el formato
especificado.
requires = IS_TIME(error_message='Debe ser HH:MM:SS!')

IS_URL

Rechaza las cadenas con URL si se cumple alguna de estas condiciones:


o

Es una cadena vaca o None

La cadena usa caracteres que no estn permitidos en un URL

La cadena no cumple alguna de las normas sintcticas del protocolo HTTP

El prefijo del URL (si se especific) no es 'http' o 'https'

El dominio de nivel superior o top-level domain no existe (si se especific un nombre


del anfitrin o host).

(Estas reglas se basan en RFC 2616[RFC2616])


Esta funcin nicamente comprueba la sintaxis del URL. No verifica que el URL, por ejemplo,
est asociado a un documento real, o que semnticamente tenga coherencia. Adems, esta
funcin automticamente antepone 'http://' al URL en caso de que se compruebe un URL
abreviado (por ejemplo 'google.ca').
Si se usa el parmetro mode='generic', cambiar el comportamiento de la funcin. En este
caso rechazar los URL que verifiquen alguna de estas condiciones:
o

La cadena es vaca o None

La cadena usa caracteres que no estn permitidos en un URL

El protocolo o URL scheme, si se especific, no es vlido

(Estas reglas se basan en RFC 2396[RFC2396])


La lista de protocolos permitidos se puede personalizar con el parmetro allowed_schemes. Si
excluyes None de la lista, entonces se rechazarn las URL abreviadas (las que no incluyan un
protocolo como 'http').
El protocolo antepuesto por defecto se puede personalizar con el parmetro prepend_scheme.
Si estableces prepend_scheme como None, entonces no se antepondr ningn protocolo. Los
URL que requieran anteponer un protocolo para su anlisis se aceptaran de todas formas,
pero el valor a devolver no se modificar.
IS_URL es compatible con el estndar Internationalized Domain Name (IDN) especificado en
RFC 3490[RFC3490]). Como consecuencia, los URL pueden ser cadenas comunes o cadenas
unicode.
Si la parte que especifica el dominio del URL (por ejemplo google.ca) contiene caracteres que
no pertenecen a US-ASCII, entonces el dominio se convertir a Punycode (definido en RFC
3492[RFC3492]). IS_URL se separa ligeramente de los estndar, y admite que se utilicen caracteres
que no pertenecen a US-ASCII en los componentes de la ruta y la consulta o query del URL.
Estos ltimos caracteres se codifican. Por ejemplo, los espacios se codifican como '%20'. El
caracter de unicode con el cdigo hexadecimal 0x4e86 se traducir como '%4e%86'.
Algunos ejemplos:
requires = IS_URL())
requires = IS_URL(mode='generic')
requires = IS_URL(allowed_schemes=['https'])
requires = IS_URL(prepend_scheme='https')

requires = IS_URL(mode='generic',
allowed_schemes=['ftps', 'https'],
prepend_scheme='https')

IS_SLUG
requires = IS_SLUG(maxlen=80, check=False, error_message='Debe ser un ttulo compacto')

Si check se establece como True comprueba si el valor de validacin es un ttulo compacto


o slug (permitiendo nicamente caracteres alfanumricos y guiones simples).
Si se especifica check como False (por defecto) convierte el valor de entrada al formato de
slug.
IS_STRONG

Comprueba y rechaza valores que no alcanzan un lmite mnimo de complejidad (normalmente


contraseas)
Example:
requires = IS_STRONG(min=10, special=2, upper=2)

Donde
o
o

min es la longitud mnima admitida para un valor


special es la cantidad mnima de caracteres especiales que debe contener una
cadena. Carcter especial es cualquier carcter incluido en !@#$%^&*(){}[]-+
upper es la cantidad mnima de maysculas admitidas.

IS_IMAGE

Este validador comprueba que un archivo subido por medio de un campo para ingresar
archivos se haya guardado en uno de los formatos especificados y que tenga las dimensiones
(ancho y alto) segn los lmites establecidos.
No comprueba el tamao mximo del archivo (para eso puedes usar IS_LENGHT). Devuelve
una falla de validacin si no se subieron datos. Soporta los formatos BMP, GIF, JPEG y PNG y
no requiere la instalacin de Python Imaging Library.
Partes de su cdigo fuente fueron tomadas de [source1]
Acepta los siguientes parmetros:
o

extensions: un iterable que contiene extensiones de archivo admitidas en minsculas

maxsize: un iterable conteniendo el ancho y el alto mximos de la imagen

minsize: un iterable conteniendo el ancho y el alto mnimos de la imagen

Puedes usar (-1, -1) como minsize para omitir la comprobacin del tamao de la imagen.
Aqu se muestran algunos ejemplos:
o

Comprobar si el archivo subido tiene alguno de los formatos de imagen soportados:

requires = IS_IMAGE()

Comprobar si el archivo subido es o bien JPEG o PNG:

requires = IS_IMAGE(extensions=('jpeg', 'png'))

Comprobar si el archivo es un PNG con un tamao mximo de 200x200 pixel:

requires = IS_IMAGE(extensions=('png'), maxsize=(200, 200))

Nota: al mostrar un formulario de edicin para una tabla que


incluye requires=IS_IMAGE() , no se mostrar la opcin checkbox delete porque al
eliminar el archivo se producira una falla durante la validacin. Para mostrar la opcin de
eliminacin delete utiliza este mtodo de validacin:

requires = IS_EMPTY_OR(IS_IMAGE())

IS_UPLOAD_FILENAME

Este validador comprueba que el nombre de la extensin de un archivo subido a travs de un


campo para ingresar archivos coincide con el criterio especificado.
No se verifica el tipo de archivo de ninguna forma. Devolver una falla de validacin si no se
suben datos.
Sus argumentos son:
o

filename: expresin regular para comprobar el nombre del archivo (sin la extensin).

extension: expresin regular para comprobar la extensin.

lastdot: qu punto se debe usar como separador del nombre y la


extensin: True indica el ltimo punto (por ejemplo "archivo.tar.gz" se separar en
"archivo.tar" + "gz") mientras que False establece el primer punto (por ejemplo
"archivo.tar.gz" se separar en "archivo" + "tar.gz").

case: 0 indica que se debe mantener la capitalizacin; 1 indica que se debe convertir a
minsculas (por defecto); 2 indica que se debe convertir a maysculas.

Si el valor no contiene un punto, las comprobaciones de extensin se harn respecto de una


cadena vaca y las comprobaciones de nombres de archivo se harn sobre la totalidad del
texto.
Ejemplos:
Comprobar si un archivo tiene la extensin pdf (sensible a maysculas):
requires = IS_UPLOAD_FILENAME(extension='pdf')

Comprobar si un archivo tiene la extensin tar.gz y su nombre comienza con backup:


requires = IS_UPLOAD_FILENAME(filename='backup.*', extension='tar.gz', lastdot=False)

Comprobar si un archivo no tiene extensin y su nombre coincide con README (sensible a


maysculas):
requires = IS_UPLOAD_FILENAME(filename='^README$', extension='^$', case=0)

IS_IPV4

Este es un validador que comprueba si el valor de un campo es una direccin IP version 4 en


su forma decimal. Se puede configurar para que fuerce el uso de direcciones segn un rango
especfico.
Se ha adoptado la expresin regular para IPv4 en ref. [regexlib]
Sus argumentos son:
o

minip es el valor ms bajo admitido para una direccin; acepta: str, por ejemplo,

192.168.0.1; iterable, por ejemplo, [192, 168, 0, 1]; int, por ejemplo, 3232235521
o

maxip es la mxima direccin admitida; igual que en el caso anterior

Los tres valores de ejemplo son iguales, ya que las direcciones se convierten a enteros para
comprobar la inclusin segn la siguiente funcin:
numero = 16777216 * IP[0] + 65536 * IP[1] + 256 * IP[2] + IP[3]

Ejemplos:
Comprobar si es una direccin IPv4 vlida:
requires = IS_IPV4()

Comprobar si es una direccin IPv4 para redes privadas:


requires = IS_IPV4(minip='192.168.0.1', maxip='192.168.255.255')

IS_UPPER

Este validador nunca devuelve un error. Convierte los valores a maysculas.


requires = IS_UPPER()

IS_NULL_OR

Obsoleto, a continuacin se describe un alias para IS_EMPTY_OR .


IS_EMPTY_OR

A veces necesitas admitir valores vacos en un campo adems de otros requisitos. Por
ejemplo un campo podra ser una fecha o bien estar vaco.
El validador IS_EMPTY_OR permite hacer lo siguiente:
requires = IS_EMPTY_OR(IS_DATE())

CLEANUP

Este es un filtro. Nunca devuelve un error. Slo elimina los caracteres cuyos cdigos
decimales ASCII no estn en la lista, por ejemplo [10, 13, 32-127].
requires = CLEANUP()

CRYPT

Este tambin es un filtro. Realiza un hash seguro del valor de entrada y se usa para evitar que
se pasen contraseas sin cifrado a la base de datos.
requires = CRYPT()

Por defecto, CRYPT usa 1000 iteraciones del algoritmo pbkdf2 combinado con SHA512 para
producir un hash de 20 byte de longitud. Las versiones anteriores de web2py usaban "md5" o
HMAC+SHA512 segn se especificara una clave o no.
Si se especifica una clave, CRYPT usa un algoritmo HMAC. La clave puede contener un prefijo
que determina el algoritmo a usar con HMAC, por ejemplo SHA512:
requires = CRYPT(key='sha512:estaeslaclave')

Esta es la sintaxis recomendada. La clave debe ser una cadena nica asociada con la base de
datos usada. La clave no se puede reemplazar una vez establecida. Si pierdes la clave, los
valores hash previos se tornan inutilizables.
Por defecto, CRYPT usa un argumento salt aleatorio, de forma que cada resultado es distinto.
Para usar un valor de salt constante, debes especificar su valor:
requires = CRYPT(salt='mivalorsalt')

O, para omitir el uso de salt:


requires = CRYPT(salt=False)

El validador CRYPT hace un hash de los valores de entrada, y esto lo hace un validador un
tanto especial. Si necesitas verificar un campo de contrasea antes de que se haga un hash,
puedes usar CRYPT en una lista de validadores, pero debes asegurarte de que sea el ltimo
de la lista, para que sea el ltimo ejecutado. Por ejemplo:

requires = [IS_STRONG(),CRYPT(key='sha512:estaeslaclave')]
CRYPT tambin recibe un argumento min_length , que toma el valor cero por defecto.

El hash resultante toma la forma alg$salt$hash , donde alg es el algoritmo utilizado, salt es
la cadena salt (que puede ser vaca), y hash es el resultado del algoritmo. En consecuencia,
el hash es un valor distinguible, permitiendo, por ejemplo, que el algoritmo se cambie sin
invalidar los hash previos. La clave, sin embargo, se debe conservar.

Validadores de base de datos


IS_NOT_IN_DB

Consideremos el siguiente ejemplo:


db.define_table('persona', Field('nombre'))
db.person.name.requires = IS_NOT_IN_DB(db, 'persona.nombre')

Esto requiere que cuando insertemos una nueva persona, su nombre se haya registrado
previamente en la base de datos db , en el campo persona.nombre . Como ocurre con todos
los dems validadores este requisito se controla en el nivel del procesamiento del formulario,
no en el nivel de la base de datos. Esto significa que hay una leve posibilidad de que, si dos
visitantes intentan insertar registros en forma simultnea con el mismo valor para
persona.nombre, esto resulta en una race condition y se aceptarn ambos registros. Por lo
tanto, es ms seguro indicar en el nivel de la base de datos que este campo debera tener un
valor nico:
db.define_table('persona', Field('nombre', unique=True))
db.persona.nombre.requires = IS_NOT_IN_DB(db, 'persona.nombre')

En este caso, si ocurriera una race condition, la base de datos generara una excepcin
OperationalError y uno de los registros sera rechazado.
El primer argumento de IS_NOT_IN_DB puede ser una conexin de la base de datos o un Set.
En este ltimo caso, estaras
correspondientes al objeto Set.

comprobando

nicamente

el

conjunto

de

valores

El siguiente cdigo, por ejemplo, no permite el registro de dos personas consecutivas con el
mismo nombre en un plazo de 10 das:
import datetime
hoy = datetime.datetime.today()
db.define_table('persona',
Field('nombre'),
Field('fechahora_registro', 'datetime', default=now))

ultimos = db(db.persona.fechahora_registro>now-datetime.timedelta(10))
db.persona.nombre.requires = IS_NOT_IN_DB(ultimos, 'persona.nombre')

IS_IN_DB

Consideremos las siguientes tablas y requisitos:


db.define_table('persona', Field('nombre', unique=True))
db.define_table('perro', Field('nombre'), Field('propietario', db.persona)
db.perro.propietario.requires = IS_IN_DB(db, 'persona.id', '%(nombre)s',
zero=T('Elige uno'))

Se controla en el nivel de los formularios para insercin, modificacin y eliminacin de perros.


Requiere que el valor de perro.propietario sea un id vlido del campo persona.id en la base
de datos db . Por este validador, el campo perro.propietario se muestra como un men
desplegable. El tercer argumento del validador es una cadena que describe los elementos del
men. En el ejemplo queremos que se vea el nombre de la persona la persona %
(nombre)s en lugar del id de la persona %(id)s . %(...)s se reemplaza por el valor del campo

entre parntesis para cada registro.


La opcin zero funciona de la misma forma que en el validador IS_IN_SET .
El primer argumento del validador puede ser una conexin de la base de datos o un Set de
DAL, como en IS_NOT_IN_DB . Esto puede ser til por ejemplo cuando queremos limitar los
registros en el men desplegable. En este ejemplo, usamos IS_IN_DB en un controlador para
limitar los registros en forma dinmica cada vez que se llama al controlador:
def index():
(...)
consulta = (db.tabla.campo == 'xyz') # en general 'xyz' suele ser una variable
db.tabla.campo.requires=IS_IN_DB(db(consulta),....)
formulario=SQLFORM(...)
if formulario.process().accepted: ...
(...)

Si quieres que el campo realice la validacin, pero no quieres un men desplegable, debes
colocar el validador en una lista.
db.perro.propietario.requires = [IS_IN_DB(db, 'persona.id', '%(nombre)s')]

En algunas ocasiones, puedes necesitar el men desplegable (por lo que no quieres usar la
sintaxis de lista anterior) pero adems quieres utilizar validadores adicionales. Para este

propsito el validador IS_IN_DB acepta un argumento adicional _and que tiene como
referencia una lista de otros validadores que deben aplicarse si el valor verificado pasa la
validacin para IS_IN_DB . Por ejemplo, para validar todos los propietarios de perros en la base
de datos que no pertenecen a un subconjunto:
subconjunto=db(db.persona.id>100)
db.perro.propietario.requires = IS_IN_DB(db, 'persona.id', '%(nombre)s',
_and=IS_NOT_IN_DB(subconjunto, 'persona.id'))
IS_IN_DB tiene un argumento booleano distinct que es por defecto False . Cuando se

establece como True evita la duplicacin de datos en la lista desplegable.


IS_IN_DB adems toma un argumento cache que funciona como el argumento cache de un

comando select.
IS_IN_DB

y selecciones mltiples

El validador IS_IN_DB tiene un atributo opcional multiple=False . Si se establece como True ,


se pueden almacenar mltiples valores en un campo. Este campo debera ser de
tipo list:reference como se describe en el Captulo 6. Tambin en ese captulo se puede ver
un ejemplo claro de selecciones mltiples o tagging . Las referencias mltiples se manejan
automticamente en los formularios de creacin y modificacin, pero estos son transparentes
para DAL. Aconsejamos especialmente el uso del plugin de jQuery multiselect para presentar
campos mltiples.

Validadores personalizados
Todos los validadores siguen el prototipo detallado a continuacin:
class Validador:
def __init__(self, *a, error_message='Error'):
self.a = a
self.e = error_message
def __call__(self, valor):
if validacion(valor):
return (procesar(valor), None)
return (valor, self.e)
def formatter(self, valor):
return formato(valor)

es decir, cuando se llama al validador, este devuelve una tupla (x, y) . Si y es None ,
entonces el valor pas la validacin y x contiene un valor procesado. Por ejemplo, si el
validador requiere que el valor sea un entero, x se convierte a int(valor) . Si el valor no pasa
la validacin, entonces x contiene el valor de entrada e y contiene un mensaje de error que
explica la falla de validacin. Este mensaje de error se usa para reportar el error en formularios
que no son aceptados.
Adems el validador puede contener un mtodo formatter . Este debe realizar la conversin
opuesta a la realizada en __call__ . Por ejemplo, tomemos como ejemplo el cdigo
de IS_DATE :
class IS_DATE(object):
def __init__(self, format='%Y-%m-%d', error_message='Debe ser YYYY-MM-DD!'):
self.format = format
self.error_message = error_message
def __call__(self, valor):
try:
y, m, d, hh, mm, ss, t0, t1, t2 = time.strptime(value, str(self.format))
valor = datetime.date(y, m, d)
return (valor, None)
except:
return (valor, self.error_message)
def formatter(self, valor):
return valor.strftime(str(self.format))

Al aceptarse los datos, el mtodo __call__ lee la cadena con la fecha del formulario y la
convierte en un objeto datetime.date usando la cadena de formato especificada en el
constructor. El objeto formatter toma el objeto datetime.date y lo convierte en una cadena
usando el mismo formato. El formatter se llama automticamente en formularios, pero
adems puedes llamarlo explcitamente para convertir objetos en funcin de un formato
apropiado. Por ejemplo:
>>> db = DAL()
>>> db.define_table('unatabla',
Field('nacimiento', 'date', requires=IS_DATE('%m/%d/%Y')))
>>> id = db.unatabla.insert(nacimiento=datetime.date(2008, 1, 1))

>>> registro = db.unatabla[id]


>>> print db.unatabla.formatter(registro.nacimiento)
01/01/2008

Cuando se requieren mltiples validadores (y se almacenan en una lista), se ejecutan en


forma ordenada y la salida de uno es pasada como entrada del prximo. La cadena se rompe
cuando uno de los validadores falla.
El caso opuesto es que, si usamos el mtodo formatter en un campo, los formatter de los
validadores asociados tambin se encadenan, pero en el orden inverso al primer caso.

Observa que como alternativa de los validadores personalizados, tambin puedes usar el
argumento onvalidate de form.accepts(...) , form.process(...) y form.validate(...) .

Validadores asociados
Normalmente los validadores se establecen por nica vez en los modelos.
Pero a veces necesitas validar un campo y que su validador dependa del valor de otro campo.
Esto se puede hacer de varias formas, en el modelo o en el controlador.
Por ejemplo, esta es una pgina que genera un formulario de registro y acepta un nombre de
usuario y una contrasea que se debe completar dos veces. Ninguno de los campos puede
estar vaco, y las dos contraseas deben coincidir:
def index():
formulario = SQLFORM.factory(
Field('nombre', requires=IS_NOT_EMPTY()),
Field('password', requires=IS_NOT_EMPTY()),
Field('verificacion_password',
requires=IS_EQUAL_TO(request.vars.password)))
if formulario.process().accepted:
pass # o realizar una accin adicional
return dict(form=form)

El mismo mecanismo se puede aplicar a los objetos FORM y SQLFORM.

Widget
Esta es una lista de widget incorporados en web2py:
SQLFORM.widgets.string.widget

SQLFORM.widgets.text.widget
SQLFORM.widgets.password.widget
SQLFORM.widgets.integer.widget
SQLFORM.widgets.double.widget
SQLFORM.widgets.time.widget
SQLFORM.widgets.date.widget
SQLFORM.widgets.datetime.widget
SQLFORM.widgets.upload.widget
SQLFORM.widgets.boolean.widget
SQLFORM.widgets.options.widget
SQLFORM.widgets.multiple.widget
SQLFORM.widgets.radio.widget
SQLFORM.widgets.checkboxes.widget
SQLFORM.widgets.autocomplete

Los primeros diez de la lista son los predeterminados para sus tipos de campos
correspondientes.
Los
widget
"options"
se
usan
en
los
validadores
de
campo IS_IN_SET o IS_IN_DB con la opcin multiple=False (el comportamiento por defecto).
El widget "multiple" se usa cuando un validador de campo es IS_IN_SET o IS_IN_DB y tiene la
opcin multiple=True . Los widget "radio" y "checkboxes" no se usan por defecto en ningn
validador, pero se pueden especificar manualmente. El widget autocomplete es especial y se
tratar en otra seccin.
Por ejemplo, para que un campo "string" se presente como textarea:
Field('comentario', 'string', widget=SQLFORM.widgets.text.widget)

Los widget pueden tambin especificarse en los campos a posteriori:


db.mitabla.micampo.widget = SQLFORM.widgets.string.widget

A veces los widget pueden recibir argumentos y debemos especificar sus valores. En este
caso se puede usar un lambda
db.mitabla.micampo.widget = lambda campo, valor: \
SQLFORM.widgets.string.widget(campo, valor, _style='color:blue')

Los widget son creadores de ayudantes y sus dos primeros argumentos son
siempre campo y valor . Los otros argumentos pueden incluir atributos comunes de

ayudantes como _style , _class etc. Algunos widget adems aceptan argumentos especiales.
En

particular SQLFORM.widgets.radio y SQLFORM.widgets.checkboxes aceptan

un

argumento style (que no debe confundirse con _style ) que se puede especificar como
"table", "ul" o "divs" para que su formstyle coincida con el del formulario que lo contiene.
Puedes crear nuevos widget o extender los predeterminados.
SQLFORM.widgets[tipo] es

una

clase

y SQLFORM.widgets[tipo].widget es

una

funcin static de la clase correspondiente. Cada funcin de widget toma dos argumentos: el
objeto campo y el valor actual de ese campo. Devuelve una representacin del widget. Por
ejemplo, el widget string se puede reescribir de la siguiente forma:
def mi_widget_string(campo, valor):
return INPUT(_name=campo.name,
_id="%s_%s" % (campo._tablename, campo.name),
_class=campo.type,
_value=valor,
requires=campo.requires)

Field('comentario', 'string', widget=mi_widget_string)

Los valores del id y la clase deben seguir las convenciones descriptas en las secciones
previas de este captulo. Un widget puede contener sus propios validadores, pero es una
buena prctica el asociar los validadores al atributo "requires" del campo y hacer que el widget
los obtenga de l.

Widget autocomplete
Hay dos usos posibles para el widget autocomplete: para autocompletar un campo que recibe
un valor de una lista o para autocompletar un campo reference (donde la cadena a
autocompletar es un valor que sustituye la referencia implementado en funcin de un id).
El primer caso es fcil:
db.define_table('categoria',Field('nombre'))
db.define_table('producto',Field('nombre'),Field('categoria'))
db.producto.categoria.widget = SQLFORM.widgets.autocomplete(
request, db.categoria.nombre, limitby=(0,10), min_length=2)

Donde limitby le indica al widget que no muestre ms de 10 sugerencias por vez,


y min_lenght le indica que el widget debe ejecutar un callback Ajax para recuperar las

sugerencias slo despus de que el usuario haya escrito al menos 2 caracteres en el campo
de bsqueda.
El segundo caso es ms complicado:
db.define_table('categoria', Field('nombre'))
db.define_table('producto', Field('nombre'),Field('categoria'))
db.producto.categoria.widget = SQLFORM.widgets.autocomplete(
request, db.categoria.nombre, id_field=db.categoria.id)

En este caso el valor de id_field le dice al widget que incluso si el valor a ser autocompletado
es

un db.categoria.nombre ,

el

valor

almacenar

es

el

correspondiente

a db.categoria.id . orderby es un parmetro opcional que le indica al widget la forma de


ordenar las sugerencias (el orden es alfabtico por defecto).
Este widget funciona con Ajax. Dnde est el callback de Ajax? En este widget hay algo de
magia. El callback es un mtodo del objeto widget en s. Cmo se expone? En web2py toda
pieza de cdigo fuente puede crear una respuesta generando una excepcin HTML. Este
widget aprovecha esta posibilidad de la siguiente forma: el widget enva una llamada Ajax al
mismo URL que gener el widget inicialmente y agrega un valor especial entre las variables de
la solicitud. Todo esto se hace en forma transparente y no requiere la intervencin del
desarrollador.
SQLFORM.grid

y SQLFORM.smartgrid

Importante: grid y smartgrid eran experimentales hasta la versin 2.0 de web2py y


presentaban vulnerabilidades relacionadas con la confidencialidad de los datos
(information leakage). grid y smartgrid ya no son experimentales, pero de todas formas no
podemos garantizar la compatibilidad hacia atrs de la capa de presentacin del grid, slo
para su API.
Estas son dos herramientas para la creacin de controles avanzados para CRUD. Proveen de
paginacin, la habilidad de navegar, buscar, ordenar, actualizar y eliminar registros usando una
sola herramienta o gadget.
El ms simple de los dos es SQLFORM.grid . Este es un ejemplo de uso:
@auth.requires_login()
def administrar_usuarios():
grid = SQLFORM.grid(db.auth_user)
return locals()

que produce la siguiente pgina:

El primer argumento de SQLFORM.grid puede ser una tabla o una consulta. El gadget de grid
proveer de acceso a los registros que coincidan con la consulta.
Antes de que nos sumerjamos en la larga lista de argumentos del gadget de grid debemos
entender cmo funciona. El gadget examina request.args para decidir qu hacer (listar, buscar,
crear, actualizar, borrar, etc.). Cada botn creado por el gadget enlaza con la misma funcin
( administrar_usuarios para el caso anterior) pero pasa distintos parmetros a request.args .
Por defecto, todos los URL generados por el grid tienen firma digital y son verificados. Esto
implica que no se pueden realizar ciertas acciones (crear, modificar, borrar) sin estar
autenticado. Estas restricciones se pueden modificar para que sean menos estrictas:

def administrar_usuarios():
grid = SQLFORM.grid(db.auth_user,user_signature=False)
return locals()

pero no es recomendable.

Por la forma en que funciona grid uno puede solamente usar un grid por funcin de
controlador, a menos que estos estn embebidos como componentes va LOAD . Para
hacer que el grid por defecto de bsqueda funcione en ms de un grid incrustado con
LOAD, debes usar un formname distinto para cada uno.
Como la funcin que contiene el grid puede por s misma manipular los argumentos de
comandos, el grid necesita saber cules argumentos debera manejar y cules no. Este es un
ejemplo de cdigo que nos permite el manejo de mltiples tablas:
@auth.requires_login()
def administrar():
tabla = request.args(0)
if not tabla in db.tables(): redirect(URL('error'))
grid = SQLFORM.grid(db[tabla], args=request.args[:1])
return locals()

el

argumento args del grid especifica

qu

argumentos

de request.args deberan

ser

recuperados por el grid y cules debera ignorar. Para nuestro caso, request.args[:1] es el
nombre de la tabla que queremos administrar y ser manejada por la funcin administrar en
s, no por el gadget.
La lista completa de argumentos que acepta el grid es la siguiente:
SQLFORM.grid(
consulta,
fields=None,
field_id=None,
left=None,
headers={},
orderby=None,
groupby=None,
searchable=True,

sortable=True,
paginate=20,
deletable=True,
editable=True,
details=True,
selectable=None,
create=True,
csv=True,
links=None,
links_in_grid=True,
upload='<default>',
args=[],
user_signature=True,
maxtextlengths={},
maxtextlength=20,
onvalidation=None,
oncreate=None,
onupdate=None,
ondelete=None,
sorter_icons=(XML('&#x2191;'), XML('&#x2193;')),
ui = 'web2py',
showbuttontext=True,
_class="web2py_grid",
formname='web2py_grid',
search_widget='default',
ignore_rw = False,
formstyle = 'table3cols',
exportclasses = None,
formargs={},

createargs={},
editargs={},
viewargs={},
buttons_placement = 'right',
links_placement = 'right'
)

fields es una lista de campos que se recuperarn de la base de datos. Tambin se usa

para especificar qu campos se mostrarn en la vista del grid.


o

field_id debe ser un campo de la tabla a usarse como ID, por ejemplo db.mitabla.id .

left es una expresin opcional left join que se utiliza para generar un ...select(left=...) .

headers es un diccionario que asocia los nombredelatabla.nombredelcampo en la

etiqueta del encabezado correspondiente, por ejemplo {'auth_user.email' : 'Correo


electrnico'}

orderby se usa como orden por defecto de los registros.

groupby se

usa

para

agrupar

la

consulta.

Utiliza

la

misma

sintaxis

que select(groupby=...) .
o

searchable , sortable , deletable , editable , details , create indica si se habilitarn las

funcionalidades de bsqueda, orden, borrar, modificar, visualizar detalles y crear nuevos


registros respectivamente.
o

selectable se puede usar para llamar a una funcin personalizada pasando mltiples

registros (se insertar una opcin checkbox para cada registro), por ejemplo
selectable = lambda ids : redirect(URL('default',
'asociar_multiples',
vars=dict(id=ids)))

paginate establece la cantidad mxima de registros por pgina.

csv si se establece como true permite que se descarguen los registros en mltiples

formatos (se detalla en otra seccin).

links se usa para mostrar columnas adicionales que pueden ser link a otras pginas.

El argumento link debe ser una lista de dict(header='nombre', body=lambda row:


A(...)) donde header es el encabezado de la nueva columna y body es una funcin

que toma un registro y devuelve un valor. En el ejemplo, el valor es un ayudante A(...) .


o

links_in_grid si se establece como False, los link solo se mostrarn en las pginas

"details" y "edit" (por lo tanto, no se mostrarn en la pgina principal del grid).


o

upload funciona de la misma forma que con SQLFORM. web2py usa la accin de ese

URL para descargar el archivo.


o

maxtextlength especifica la longitud mxima del texto que se mostrar para cada valor

de un campo, en la vista del grid. Este valor se puede sobrescribir en funcin del campo
usando maxtextlengths ,
un
diccionario
de
elementos
'nombredelatabla.nombredelcampo': longitud, por ejemplo {'auth_user.email': 50} .
o

onvalidation , oncreate , onupdate y ondelete son funciones de retorno o callback.

Todas excepto ondelete reciben un objeto form como argumento.


o

sorter_icons es una lista de cadenas (o ayudantes) que se usarn para presentar las

opciones de orden ascendente y descendente para cada campo.


o

ui si se especifica como 'web2py' generar nombres de clase conforme a la notacin

web2py, si se especifica jquery-ui generar clases conforme a jQuery UI, pero tambin
se puede especificar un conjunto de nombres de clases para los distintos componentes
de grid:
ui = dict(
widget='',
header='',
content='',
default='',
cornerall='',
cornertop='',
cornerbottom='',
button='button',
buttontext='buttontext button',

buttonadd='icon plus',
buttonback='icon leftarrow',
buttonexport='icon downarrow',
buttondelete='icon trash',
buttonedit='icon pen',
buttontable='icon rightarrow',
buttonview='icon magnifier')

search_widget permite sobrescribir el widget de bsqueda por defecto. Para ms

detalles recomendamos consultar el cdigo fuente en "gluon/sqlhtml.py"


o

showbuttontext permite usar botones sin texto (solo se mostrarn iconos)

_class es la clase del elemento que contiene grid

showbutton permite deshabilitar los botones.

exportclasses recibe un diccionario de tuplas. Por defecto se define de la siguiente

forma:
csv_with_hidden_cols=(ExporterCSV, 'CSV (columnas ocultas)'),
csv=(ExporterCSV, 'CSV'),
xml=(ExporterXML, 'XML'),
html=(ExporterHTML, 'HTML'),
tsv_with_hidden_cols=(ExporterTSV, 'TSV (Compatible con Excel, columnas ocultas)'),
tsv=(ExporterTSV, 'TSV (Compatible con excel)'))

ExporterCSV, ExporterXML, ExporterHTML y ExporterTSV estn definidos en


gluon/sqlhtml.py. Puedes usarlos como ejemplo para crear tus propios Exporter. Si pasas
un diccionario como dict(xml=False, html=False) deshabilitars los formatos de
exportacin html y xml.

formargs se

pasa

todo

objeto

SQLFORM

que

use

el

grid,

mientras createargs y viewargs se pasan solo a los SQLFORM de creacin, edicin y


detalles.

formname , ignore_rw y formstyle se pasan a los objetos SQLFORM usados por el

grid para los formularios de creacin y modificacin.


o

buttons_placement y links_placement toman un parmetro comprendido en ('right',

'left', 'both') que especifica la posicin en la visualizacin de los registros para los botones
(o los link).
deletable

, editable y details son normalmente valores booleanos pero pueden ser

funciones que reciben un objeto Row e indican si un registro se debe mostrar o no.
Un SQLFORM.smartgrid tiene una apariencia muy similar a la de un grid ; de hecho contiene
un grid, pero est diseado para aceptar como argumento una tabla, no una consulta, y para
examinar esa tabla y un conjunto de tablas asociadas.
Por ejemplo, consideremos la siguiente estructura de tablas:
db.define_table('padre', Field('nombre'))
db.define_table('hijo', Field('nombre'), Field('padre', 'reference padre'))

Con SQLFORM.grid puedes crear una lista de padres:


SQLFORM.grid(db.padre)

todos los hijos:


SQLFORM.grid(db.hijo)

y todos los padres e hijos en una tabla:


SQLFORM.grid(db.padre, left=db.hijo.on(db.hijo.padre==db.padre.id))

Con SQLFORM.smartgrid puedes unir toda la informacin en un gadget que combine ambas
tablas:
@auth.requires_login()
def administrar():
grid = SQLFORM.smartgrid(db.padre, linked_tables=['hijo'])
return locals()

que se visualiza de este modo:

Observa los link adicionales "hijos". Podramos crear los links adicionales usando
un grid comn, pero en ese caso estaran asociados a una accin diferente. Con
un samartgrid estos link se crean automticamente y son manejados por el mismo gadget.
Adems, observa que cuando se hace clic en el link "hijos" para un padre determinado, solo
se obtiene la lista de hijos para ese padre (obviamente) pero adems observa que si uno
ahora intenta agregar un hijo, el valor del padre para el nuevo hijo se establece
automticamente al del padre seleccionado (que se muestra en el breadcrumbs o migas de
pan asociado al gadget). El valor de este campo se puede sobrescribir. Podemos prevenir su
sobreescritura aplicndole el atributo de solo lectura:

@auth.requires_login():
def administrar():
db.hijo.padre.writable = False
grid = SQLFORM.smartgrid(db.padre,linked_tables=['hijo'])
return locals()

Si el argumento linked_tables no se especifica, todas las tablas asociadas se enlazarn. De


todos modos, para evitar exponer en forma accidental la informacin, es recomendable listar
explcitamente las tablas que se deben asociar.
El siguiente cdigo crea una interfaz de administracin muy potente para todas las tablas del
sistema:
@auth.requires_membership('managers'):
def administrar():
tabla = request.args(0) or 'auth_user'
if not tabla in db.tables(): redirect(URL('error'))
grid = SQLFORM.smartgrid(db[tabla], args=request.args[:1])
return locals()

El smargrid toma los mismos argumentos como grid y algunos ms, con algunos detalles a
tener en cuenta:
o

El primer argumento debe ser una tabla, no una consulta

Hay un argumento adicional llamado constraints que consiste de un diccionario


compuesto por elementos 'nombredelatabla': consulta, que se puede usar para restringir
el acceso a los registros mostrados en el grid correspondiente a nombredelatabla.

Hay un argumento adicional llamado linked_tables que es una lista de nombres de


tabla a los que se puede acceder a travs del smartgrid.

divider permite

especificar

un

carcter

que

se

usar

en

el

navegador

breadcrumb, breadcrumb_class especificar la clase del elemento breadcrumb


o

Todos
los
argumentos
excepto
el
de
la
tabla, args , linked_tables y user_signatures aceptan un diccionario segn se detalla
ms abajo.

Tomemos como ejemplo el grid anterior:

grid = SQLFORM.smartgrid(db.padre, linked_tables=['hijo'])

Esto nos permite acceder tanto a db.padre como a db.hijo . Excepto para el caso de los
controles de navegacin, para cada tabla individual, una tabla inteligente o smarttable no es
otra cosa que un grid. Esto significa que, en este caso, un smartgrid puede crear un grid para
el padre y otro para el hijo. Podra interesarnos pasar distintos parmetros a cada grid. Por
ejemplo, conjuntos distintos de parmetros searchable .
Si para un grid deberamos pasar un booleano:
grid = SQLFORM.grid(db.padre, searchable=True)

en cambio, para un smartgrid deberamos pasar un diccionario de booleanos:


grid = SQLFORM.smartgrid(db.padre, linked_tables=['hijo'],
searchable= dict(padre=True, hijo=False))

De este modo hemos especificado que se puedan buscar padres, pero que no se puedan
buscar hijos en funcin de un padre, ya que no deberan ser tantos como para que sea
necesario usar un widget de bsqueda).

Los gadget grid y smartgrid han sido incorporados al ncleo en forma definitiva pero estn
marcados como funcionalidades experimentales porque el diseo de pgina actual
generado y el conjunto exacto de parmetros que aceptan puede ser objeto de
modificaciones en caso de que se agreguen nuevas caractersticas.
grid y smartgrid no realizan un control automatizado de permisologa como en el caso de

crud, pero es posible integrar el uso de auth por medio de controles especficos:
grid = SQLFORM.grid(db.auth_user,
editable = auth.has_membership('managers'),
deletable = auth.has_membership('managers'))

o
grid = SQLFORM.grid(db.auth_user,
editable = auth.has_permission('edit','auth_user'),
deletable = auth.has_permission('delete','auth_user'))

El smartgrid es el nico gadget de web2py que muestra el nombre de la tabla y requiere tanto
los parmetros singular como plural. Por ejemplo un padre puede tener un "Hijo" o muchos
"Hijos". Por lo tanto, un objeto tabla necesita saber sus nombres correspondientes para el
singular y el plural. Normalmente web2py los infiere, pero adems los puedes especificar en
forma explcita:
db.define_table('hijo', ..., singular="Hijo", plural="Hijos")

o con:
db.define_table('hijo', ...)
db.child._singular = "Hijo"
db.child._plural = "Hijos"

Adems, deberan traducirse automticamente usando el operador T .


Los valores singular y plural se usan luego en smartgrid para proveer los nombres adecuados
de los encabezados y links.

Control de acceso
web2py incluye un mecanismo de Control de Acceso Basado en Roles (RBAC) potente y
personalizable.
He aqu una definicin en Wikipedia:
"... el Control de Acceso Basado en Roles (RBAC) es una tcnica para restringir el acceso
al sistema a usuarios autorizados. Es una nueva alternativa del Control de Acceso
Obligatorio (MAC) y el Control de Acceso Discrecional (DAC). A veces se refiere a RBAC
como seguridad basada en roles (role-based security)
RBAC es una tecnologa para el control de acceso independiente de las reglas
implementadas y flexible lo suficientemente potente para emular DAC y MAC. Asimismo,
MAC puede emular RBAC si la configuracin de roles (role graph) se restringe a un rbol
en lugar de un conjunto parcialmente ordenado.
Previamente al desarrollo de RBAC, MAC y DAC eran considerados el nico modelo
conocido para control de acceso: si un modelo no era MAC, se lo consideraba modelo
DAC, y viceversa. La investigacin de los finales de la dcada de 1990 demostr que
RBAC no cuadra en ninguna de esas categoras.
En el mbito de una organizacin, los roles se crean para varias funciones de trabajo. La
permisologa para realizar ciertas operaciones es asignada a roles especficos. Se asignan a
los miembros del personal (u otros usuarios del sistema) roles particulares, y por medio de
esas asignaciones de roles adquieren los permisos para acceder a funciones particulares del
sistema. A diferencia de los controles de acceso basados en el contexto (contextbased CBAC), RBAC no revisa el contexto del mensaje (como por ejemplo la direccin de
origen de la conexin).

Como no se asigna a los usuarios permisos directamente, sino que solo los obtienen a travs
de su rol (o roles), el manejo de derechos individuales consta simplemente de asociar los
roles apropiados a un usuario determinado; esto simplifica las operaciones ms comunes,
como por ejemplo agregar un usuario o cambiar a un usuario de departamento.
RBAC difiere de las listas de control de acceso (ACLs) usadas normalmente en los sistemas
de control automtico de acceso tradicionales en que asigna permisos para operaciones
especficas que tienen un significado para la organizacin, no solo para objetos de datos de
bajo nivel. Por ejemplo, se podra usar una lista de control de acceso para otorgar o denegar
el acceso a escritura a un archivo determinado del sistema, pero eso no informara sobre la
forma en que se puede modificar ese archivo ..."
La clase de web2py que implementa RBAC se llama Auth.
Auth necesita (y define) las siguientes tablas:
o

almacena

auth_user

el

nombre

del

usuario,

direccin

de

correo

electrnico, contrasea y estado (pendiente de registro, aceptado,


bloqueado)
o

auth_group

almacena los grupos o roles para usuarios en una estructura

muchos-a-muchos. Por defecto, cada usuario pertenece a su propio grupo,


pero un usuario puede estar incluido en mltiples grupos, y cada grupo
contener mltiples usuarios. Un grupo es identificado por su rol y
descripcin.
o

auth_membership

enlaza usuarios con grupos en una estructura muchos-a-

muchos.
o

auth_permission

enlaza grupos con permisos. Un permiso se identifica por

un nombre y opcionalmente, una tabla y un registro. Por ejemplo, los


miembros de cierto grupo pueden tener permisos "update" (de
actualizacin) para un registro especfico de una tabla determinada.
o

auth_event

registra los cambios en las otras tablas y el acceso otorgado a

travs de CRUD a objetos controlados con RBAC.


o

auth_cas

se usa para el Servicio Central de Autenticacin (CAS). Cada

aplicacin web2py es un proveedor de CAS y puede opcionalmente


consumir el servicio CAS.

El esquema de acceso se ha reproducido grficamente en la imagen de abajo:

En un principio, no hay una restriccin de los nombres de roles o permisos; el desarrollador


puede crearlos de acuerdo a los requerimientos de nombres de la organizacin. Una vez que
estos se han creado, web2py provee de una API para comprobar si un usuario est
autenticado, si un usuario es miembro de un grupo determinado, y/o si el usuario es
miembro de cualquier grupo que tenga asignado un permiso determinado.
web2py provee adems de decoradores para la restriccin de acceso a cualquier funcin en
base al sistema de autenticacin o login, membresa o membership y permisos.
Adems, web2py es capaz de interpretar automticamente algunos permisos especiales,
como por ejemplo, aquellos asociados a los mtodos CRUD (create, read, update, delete) y
puede llevar un control automtico sin necesidad del uso de decoradores.
En este captulo, vamos a tratar sobre distintas partes de RBAC caso por caso.

Autenticacin
Para poder usar RBAC, debemos identificar a los usuarios. Esto significa que deben
registrarse (o ser registrados) e ingresar al sistema (log in).
Auth provee de mltiples mtodos de autenticacin. El mtodo por defecto consiste en
identificar a los usuarios segn la tabla local auth_user . Como alternativa, se puede
registrar a los usuarios por medio de sistemas de autenticacin de terceros y proveedores
desingle sign on como Google, PAM, LDAP, Facebook, LinkedIn, Dropbox, OpenID,
OAuth, etc...
Para comenzar a usar

Auth

, debes por lo menos colocar este cdigo en un archivo del

modelo, que tambin viene por defecto en la aplicacin "welcome" de web2py y supone el
uso de un objeto de conexin llamado db :
from gluon.tools import Auth
auth = Auth(db)
auth.define_tables()

Auth tiene un argumento opcional

secure=True

, que forzar la autenticacin a travs de

HTTPS.
El campo password de la tabla db.auth_user tiene un validador CRYPT por defecto
que requiere una hmac_key . En aplicaciones heredadas de web2py deberas ver

un argumento extra pasado al constructor de Auth: hmac_key =


Auth.get_or_create_key()

. Esta ltima es una funcin que lee una clave HMAC

desde "private/auth.key" en la carpeta de la aplicacin. Si el archivo no existe,


crea una hmac_key aleatoria. Si la misma base de datos auth es compartida por
mltiples aplicaciones, asegrate de que tambin usen la misma hmac_key .
Esto ya no es necesario para aplicaciones nuevas porque las contraseas
son saladas segn un salt aleatorio e individual.

Por defecto, web2py usa la direccin de correo electrnico como nombre de usuario para
el login.
Si
quieres
autenticar
a
los
usuarios
con username debes
establecer auth.define_tables(username=True) .
Cuando la base de datos de auth es compartida por varias aplicaciones deberas deshabilitar
las migraciones: auth.define_tables(migrate=False=) .
Para exponer Auth, necesitas adems la siguiente funcin en un controlador (por ejemplo
en "default.py"):
def user(): return dict(form=auth())

El objeto auth y la accin user estn definidos por defecto en la aplicacin de


andamiaje.

web2py incluye adems una vista de ejemplo "welcome/views/default/user.html" para


convertir la funcin correctamente, similar a:
{{extend 'layout.html'}}
<h2>{{=T( request.args(0).replace('_',' ').capitalize() )}}</h2>
<div id="web2py_user_form">
{{=form}}
{{if request.args(0)=='login':}}
{{if not 'register' in auth.settings.actions_disabled:}}
<br/><a href="{{=URL(args='register')}}">regstrese</a>
{{pass}}
{{if not 'request_reset_password' in auth.settings.actions_disabled:}}
<br/>
<a href="{{=URL(args='request_reset_password')}}">olvid mi contrasea</a>

{{pass}}
{{pass}}
</div>

Observa que esta funcin simplemente muestra un

form

y por lo tanto se puede

personalizar usando la notacin comn para formularios. El nico problema es que el


formulario producido por medio de form=auth() depende de request.args(0) ; por lo tanto,
si reemplazas el formulario de login
condicional

if

auth()

con uno personalizado, puedes necesitar un

en la vista como el siguiente:

{{if request.args(0)=='login':}}...formulario de autenticacin personalizado...{{pass}}

El controlador anterior expone mltiples acciones:


http://.../[app]/default/user/register
http://.../[app]/default/user/login
http://.../[app]/default/user/logout
http://.../[app]/default/user/profile
http://.../[app]/default/user/change_password
http://.../[app]/default/user/verify_email
http://.../[app]/default/user/retrieve_username
http://.../[app]/default/user/request_reset_password
http://.../[app]/default/user/reset_password
http://.../[app]/default/user/impersonate
http://.../[app]/default/user/groups
http://.../[app]/default/user/not_authorized

register permite a un usuario registrarse. Se integra con CAPTCHA,


aunque la opcin no est habilitada por defecto. Tambin est integrado
con una calculadora de entropa definida en "web2py.js". La calculadora
indica la fortaleza de la nueva contrasea. Puedes usar el
validador IS_STRONG para prevenir que web2py acepte contraseas
dbiles.

login permite a un usuario registrado el acceso al sistema o login (si el


registro del usuario se ha verificado o no se requiere verificacin, si se
aprov o no requiere aprobacin, y si no est bloqueado).

logout hace lo que esperaras que haga pero adems, como los dems
mtodos, registra la accin y se puede usar adems para activar otra
accin o event.

profile permite a los usuarios editar sus datos de registro, es decir, el


contenido de la tabla auth_user . Observa que esta tabla no tiene una
estructura fija y se puede personalizar.

change_password permite a los usuarios cambiar su contrasea en


forma segura.

verify_email. Si la verificacin de correo electrnico est habilitada, los


usuarios, al registrarse, reciben un correo con un link para verificar su
informacin de correo. El link refiere a esta misma accin.

retrieve_username. Por defecto, Auth usa email y contrasea para el


login, pero puede, opcionalmente, utilizar username en su lugar. Para este
ltimo caso, si un usuario olvida su nombre de usuario, el
mtodo retrieve_username permite al usuario ingresar la direccin de correo
electrnico para que se le enve su nombre de usuario.

request_reset_password. Permite a los usuarios que olvidaron su


contrasea que soliciten una nueva. Recibirn una confirmacin por correo
electrnico enlazada a la accin reset_password.

impersonate permite a un usuario adoptar las credenciales de otro


o suplirlo en forma temporal. Esto es importante para propsitos de
depuracin. request.args[0] es el id del usuario que se va a suplir. Esto se
permite nicamente si el usuario verifica has_permission('impersonate',
db.auth_user, user_id) . Puedes usar auth.is_impersonating() para comprobar si el
usuario actual est supliendo a otro usuario.

groups lista los grupos en los que est incluido el usuario como
miembro.

not_authorized muestra un mensaje de error cuando el usuario ha


intentado hacer sin permisos que lo habiliten.

navbar es un ayudante
login/registrarse/etc.

que

genera

una

barra

con

links

Logout, profile, change_password, impersonate, y groups requieren un usuario autenticado.


Por defecto todos estos recursos se exponen, pero es posible restringir el acceso a un
subconjunto de las acciones.
Todos los mtodos descriptos arriba se pueden extender o reemplazar creando una subclase
de Auth.
Todos los mtodos de arriba se puede usar en acciones separadas. Por ejemplo:
def milogin(): return dict(formulario=auth.login())
def miregistro(): return dict(formulario=auth.register())
def miperfil(): return dict(formulario=auth.profile())
...

Para restringir el acceso a funciones a aquellos usuarios que se hayan autenticado


nicamente, decora la funcin como en el siguiente ejemplo
@auth.requires_login()
def hola():
return dict(message='hola %(first_name)s' % auth.user)

Toda funcin se puede decorar, no slo las acciones expuestas. Por supuesto que esto es
todava un ejemplo realmente simple de control de acceso. Ms adelante trataremos sobre
ejemplos ms complicados.
auth.user_groups
auth.user

contiene una copia de los registros en db.auth_user para el usuario

actualmente autenticado o None en su defecto. Tambin est auth.user_id que


es lo mismo que auth.user.id (es decir, el id del usuario actualmente
autenticado) o None . En forma similar, auth.user_groups contiene un diccionario
donde cada clave es el id del grupo del cual el actual usuario autenticado es
miembro, el valor asociado a la clave, es el rol correspondiente.

El

decorador

decoradores

auth.requires_login()

auth.requires_*

as

como

tambin

los

dems

toman un argumento opcional otherwise . Se puede especificar

como una cadena que indica a dnde redirigir al usuario si falla la autenticacin o como un
objetocallable. Este objeto se llama en caso de que fracase la accin.

Restricciones al registro de usuarios


Si quieres permitir a los visitantes que se registren pero que no tengan acceso hasta que su
registro se haya aprobado por el administrador:
auth.settings.registration_requires_approval = True

Puedes aprobar un registro de usuario a travs de la interfaz appadmin. Examina la


tabla auth_user .
Los
registros
de
usuarios
pendientes
tienen
un
campo

registration_key

cuyo valor es "pending". Un registro de usuario est aprobado

cuando se establece el campo de este valor como vaco.


Con la interfaz appadmin, puedes tambin bloquear a un usuario para que no pueda acceder.
Busca al usuario en la tabla auth_user y establece el registration_key a "bloqueado". Los
usuarios "bloqueados" no tienen permitido el acceso. Observa que esto evitar que un
visitante se autentique pero no forzar al visitante que ya est autenticado para que cierre su
sesin. Se puede usar la palabra "disabled" en lugar de "blocked" si se prefiere, con el
mismo resultado.
Adems puedes bloquear completamente el acceso a la pgina para registro de usuarios con
esta instruccin:
auth.settings.actions_disabled.append('register')

Si quieres permitir que todos se registren y automticamente ser autenticados luego de


registrarse pero de todas formas quieres enviar un correo de verificacin para que no
puedan autenticarse nuevamente luego de cerrar la sesin, a menos que hayan completado
las instrucciones en el correo, puedes hacerlo de la siguiente manera:
auth.settings.registration_requires_approval = True
auth.settings.login_after_registration = True

Otros mtodos de Auth se pueden restringir de la misma forma.

Integracin con OpenID, Facebook, etc.


Puedes usar el sistema de Control de Acceso Basado en Roles de web2py y autenticar con
otros servicios como OpenID, Facebook, LinkedIn, Google, Dropbox, MySpace, Flickr,
etc.
La forma ms fcil es usar Janrain Engage (antiguamente RPX) (Janrain.com).
Dropbox se tratar como caso especial en el captulo 14, porque implica ms que
simplemente autenticacin, tambin tiene servicios de almacenamiento para usuarios
autenticados.
Janrain Engage es un servicio que provee autenticacin con middleware. Puedes registrarte
en Janrain.com, registrar un dominio (el nombre de tu app) y el conjunto de los URL que
vas a usar, y el sistema te proveer de una clave de acceso a la API.
Ahora edita el modelo de tu aplicacin de web2py y coloca las siguientes lneas en alguna
parte despus de la definicin del objeto auth :
from gluon.contrib.login_methods.rpx_account import RPXAccount
auth.settings.actions_disabled=['register','change_password','request_reset_password']
auth.settings.login_form = RPXAccount(request,
api_key='...',
domain='...',
url = "http://tu-direccion-externa/%s/default/user/login" % request.application)

La primer lnea importa el nuevo mtodo de autenticacin, la segunda lnea deshabilita el


registro de usuarios local, y la tercera lnea le indica a web2py que debe usar el mtodo
RPX de autenticacin. Debes ingresar tu propia api_key provista por Janrain.com, el
dominio que hayas seleccionado al registrar la app y la

url

externa de tu pgina de login.

Para obtener los datos ingresa a janrain.com, luego ve a [Deployment][Application


Settings]. En la parte derecha est la "Application Info" (informacin de la aplicacin), la
api_key se llama "API Key (Secret)".
El dominio es "Application Domain" sin el "https://" inicial y sin el ".rpxnow.com/" final.
Por ejemplo: si has registrado un sitio web como "seguro.misitioweb.org", Janrain lo
devuelve como el dominio "https://seguro.misitioweb.rpxnow.com".

Cuando un nuevo usuario se autentica por primera vez, web2py crea un nuevo registro
en db.auth_user asociado al usuario. Utilizar el campo registration_id para almacenar la
clave id nica de autenticacin para el usuario. Practicamente todo mtodo de autenticacin
provee tambin de nombre de usuario, correo, primer nombre y apellido pero eso no est
garantizado. Los campos que se devuelven dependen del mtodo de autenticacin elegido
por el usuario. Si el mismo usuario se autentica dos veces consecutivas utilizando distintos
mecanismos de autenticacin (por ejemplo una vez con OpenID y luego con Facebook),
Janrain podra no notarlo ya que el mismo usuario puede tener asignado otro registration_id

Puedes personalizar el mapeo de datos entre los datos provistos por Janrain y la
informacin almacenada en db.auth_user . Aqu mostramos un ejemplo para Facebook:
auth.settings.login_form.mappings.Facebook = lambda profile:\
dict(registration_id = profile["identifier"],
username = profile["preferredUsername"],
email = profile["email"],
first_name = profile["name"]["givenName"],
last_name = profile["name"]["familyName"])

Las claves en el diccionario son campos en

db.auth_user

y los valores son entradas de

datos en el objeto del perfil provisto por Janrain. Consulta la documentacin en lnea de
Janrain para ms detalles sobre el objeto del perfil.
Janrain adems mantendr estadsticas sobre los ingresos del usuario.
Este formulario de autenticacin est completamente integrado con el sistema de Control de
Acceso Basado en Roles y por lo tanto puedes crear grupos, asignar membresas, permisos,
bloquear usuarios, etc.
El servicio bsico gratuito de Janrain permite hasta 2500 usuarios nicos
registrados por ao). Para una mayor cantidad de usuarios hace falta un
upgrade a alguno de sus distintos niveles de servicios pagos. Si prefieres no
usar Janrain y quieres usar un mtodo distinto de autenticacin (LDAP, PAM,
Google, OpenID, OAuth/Facebook, LinkedIn, etc.) puedes hacerlo. La API para
este propsito se describe ms adelante en este captulo.

CAPTCHA y reCAPTCHA
Para impedir que los spammer y bot se registren en tu sitio, deberas solicitar el registro a
travs de CAPTCHA. web2py soporta reCAPTCHA recaptcha por defecto. Esto se debe a que
reCAPTCHA tiene un excelente diseo, es libre, accesible (puede leer las a los visitantes),
fcil de configurar, y no requiere la instalacin de ninguna librera de terceros.
[

Esto es lo que necesitas hacer para usar reCAPTCHA:


o

Registrarte en reCAPTCHA[recaptcha] y obtener el par (PUBLIC_KEY,


PRIVATE_KEY) para tu cuenta. Estas son simplemente dos cadenas de
texto.

Agrega el siguiente cdigo a tu modelo luego de la definicin del


objeto auth :

from gluon.tools import Recaptcha


auth.settings.captcha = Recaptcha(request,
'PUBLIC_KEY', 'PRIVATE_KEY')

reCAPTCHA podra no funcionar si accedes al sitio desde 'localhost' o '127.0.0.1', porque


est limitado para funcionar solamente con sitios pblicamente accesibles.
El constructor de

Recaptcha

toma algunos argumentos opcionales:

Recaptcha(..., use_ssl=True, error_message='invlido', label='Verificar:', options='')

Observa el
options

use_ssl=False

por defecto.

puede ser una cadena de configuracin, por ejemplo

options="theme:'white',

lang:'es'"

Ms detalles: reCAPTCHA recaptchagoogle y customizing.


[

Si no quieres usar reCAPTCHA, puedes examinar la definicin de la clase

Recaptcha

en

"gluon/tools.py", ya que puedes fcilmente integrar la autenticacin con otros sistemas


CAPTCHA.
Observa que

Recaptcha

es slo un ayudante que extiende

DIV

. Genera un campo ficticio

que realiza la validacin usando el servicio reCaptcha y, por lo tanto, se puede usar en
cualquier formulario, incluso los formularios FORM definidos por el usuario.
formulario = FORM(INPUT(...), Recaptcha(...), INPUT(_type='submit'))

Puedes inyectarlo en cualquier clase de SQLFORM de esta forma:


formulario = SQLFORM(...) or SQLFORM.factory(...)
formulario.element('table').insert(-1,TR('',Recaptcha(...),''))

Personalizacin de
La llamada a
auth.define_tables()

Auth

define todas las tablas Auth que no se hayan definido previamente. Esto significa que si as
lo quisieras, podras definir tu propia tabla auth_user .
Hay algunas formas distintas de personalizar auth. La forma ms simple es agregando
campos extra:
## despus de auth = Auth(db)
auth.settings.extra_fields['auth_user']= [
Field('direccion'),
Field('ciudad'),
Field('codigo_postal'),
Field('telefono')]
## antes de auth.define_tables(username=True)

Puedes declarar campos extra no solo para la tabla "auth_user" sino tambin para las otras
tablas "auth_". Es recomendable el uso de extra_fields porque no crear ningn conflicto en
el mecanismo interno.
Otra forma de hacer lo mismo, aunque no es recomendable, consiste en definir nuestras
propias tablas auth. Si una tabla se declara antes de auth.define_tables() es usada en lugar
de la tabla por defecto. Esto se hace de la siguiente forma:
## despus de auth = Auth(db)
db.define_table(
auth.settings.table_user_name,
Field('first_name', length=128, default=''),
Field('last_name', length=128, default=''),
Field('email', length=128, default='', unique=True), # requerido
Field('password', 'password', length=512,
readable=False, label='Password'),
Field('address'),
Field('city'),
Field('zip'),
Field('phone'),

# requerido

Field('registration_key', length=512,

# requerido

writable=False, readable=False, default=''),


Field('reset_password_key', length=512,

# requerido

writable=False, readable=False, default=''),


Field('registration_id', length=512,

# requerido

writable=False, readable=False, default=''))

## no te olvides de los validadores


auth_table_especial = db[auth.settings.table_user_name] # obtiene auth_table_especial
auth_table_especial.first_name.requires = \
IS_NOT_EMPTY(error_message=auth.messages.is_empty)
auth_table_especial.last_name.requires = \
IS_NOT_EMPTY(error_message=auth.messages.is_empty)
auth_table_especial.password.requires = [IS_STRONG(), CRYPT()]
auth_table_especial.email.requires = [
IS_EMAIL(error_message=auth.messages.invalid_email),
IS_NOT_IN_DB(db, auth_table_especial.email)]

auth.settings.table_user = auth_table_especial # le dice a auth que use la tabla especial

## antes de auth.define_tables()

Puedes definir cualquier campo que quieras, y puedes cambiar los validadores pero no
puedes eliminar los campos marcados como "requerido" en el ejemplo.
Es importante que los campos "password", "registration_key", "reset_password_key" y
"registration_id" tengan los valores readable=False y writable=False , porque no debe
permitirse que los usuarios los puedan manipular libremente.
Si agregas un campo llamado "username", se utilizar en lugar de "email" para el acceso. Si
lo haces, tambin debers agregar un validador:
auth_table.username.requires = IS_NOT_IN_DB(db, auth_table.username)

Personalizacin de los nombres de tablas


Los nombres utilizados de las tablas

Auth

Auth

se almacenan en

auth.settings.table_user_name = 'auth_user'
auth.settings.table_group_name = 'auth_group'
auth.settings.table_membership_name = 'auth_membership'
auth.settings.table_permission_name = 'auth_permission'
auth.settings.table_event_name = 'auth_event'

Se pueden cambiar los nombres de las tablas cambiando los valores de las variables de
arriba despus de la definicin del objeto auth y antes de la definicin de sus tablas. Por
ejemplo:
auth = Auth(db)
auth.settings.table_user_name = 'persona'
#...
auth.define_tables()

Tambin se pueden recuperarlas tablas, independientemente de sus nombres actuales, con


auth.settings.table_user
auth.settings.table_group
auth.settings.table_membership
auth.settings.table_permission
auth.settings.table_event

Otros mtodos de acceso y formularios de


autenticacin
Auth provee de mltiples mtodos y tcnicas para crear nuevos mtodos de autenticacin.
Cada mtodo de acceso soportado tiene su correspondiente archivo en la carpeta
gluon/contrib/login_methods/

Puedes consultar la documentacin en los mismos archivos para cada mtodo de acceso,
pero aqu mostramos algunos ejemplos.

En primer lugar, necesitamos hacer una distincin entre dos tipos de mtodos alternativos
de acceso:
o

mtodos de acceso que usan el formulario de autenticacin de web2py


(aunque verifican las credenciales fuera de web2py). Un ejemplo es LDAP.

mtodos de acceso que requieren un formulario single-sign-on (como por


ejemplo Google o Facebook).

Para el ltimo caso, web2py nunca obtiene las credenciales de acceso, solo un parmetro de
acceso enviado por el proveedor del servicio. El parmetro o token se almacena
en db.auth_user.registration_id .
Veamos ejemplos del primer caso:
Bsico
Digamos que tienes un servicio de autenticacin, por ejemplo en el url
https://basico.example.com

que acepta la autenticacin de acceso bsica. Eso significa que el servidor acepta
solicitudes con un encabezado del tipo:
GET /index.html HTTP/1.0
Host: basico.example.com
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

donde la ltima cadena es la codificacin en base64 de la cadena usuario:contrasea. El


servicio responde con 200 OK si el usuario est autorizado y 400, 401, 402, 403 o 404 en
su defecto.
Quieres ingresar el usuario y contrasea usando el formulario estndar de

Auth

`y verificar

que las credenciales sean correctas para el servicio. Todo lo que debes hacer es agregar el
siguiente cdigo en tu aplicacin:
from gluon.contrib.login_methods.basic_auth import basic_auth
auth.settings.login_methods.append(
basic_auth('https://basico.example.com'))

Observa que

auth.settings.login_methods

es una lista de mtodos de autenticacin que se

ejecutan en forma secuencial. Por defecto se establece como


auth.settings.login_methods = [auth]

Cuando se agrega un mtodo alternativo, por ejemplo

basic_auth

, Auth primero intenta dar

acceso al visitante segn el contenido de auth_user , y si eso falla, trata el prximo mtodo
de autenticacin en la lista. Si un mtodo tiene xito en la autenticacin del usuario, y si se
verifica auth.settings.login_methods[0]==auth , Auth acta de la siguiente manera:
o

si el usuario no existe en la tabla auth_user , se crea un nuevo usuario y


se almacenan los campos username/email y password.

si el usuario existe en auth_user pero la contrasea aceptada no coincide


con la almacenada anteriormente, la contrasea antigua es reemplazada
con la nueva (observa que las contraseas son siempre almacenadas
como hash a menos que se especifique lo contrario).

Si no desea almacenar las la nueva contrasea en

auth_user

orden de los mtodos de autenticacin, o eliminar

auth

, entonces basta con cambiar el

de la lista. Por ejemplo:

from gluon.contrib.login_methods.basic_auth import basic_auth


auth.settings.login_methods = \
[basic_auth('https://basico.example.com')]

Esto es vlido para cualquier otro de los mtodos de acceso tratados.


SMTP y Gmail
Puedes verificar las credenciales de acceso usando un servidor remoto SMTP, por ejemplo
Gmail; por ejemplo, autenticas al usuario si el correo y la contrasea que proveen son
credenciales vlidas para acceder al servicio SMTP de Gmail ( smtp.gmail.com:567 ). Todo
lo que se requiere es el siguiente cdigo:
from gluon.contrib.login_methods.email_auth import email_auth
auth.settings.login_methods.append(
email_auth("smtp.gmail.com:587", "@gmail.com"))

El primer argumento de

email_auth

es la direccin:puerto del servidor SMTP. El segundo

argumento es el dominio del correo.


Esto funciona con cualquier servicio de correo que requiera autenticacin con TLS.
PAM
La autenticacin usando los Pluggable Authentication Modules (PAM) funciona en forma
parecida los casos anteriores. Permite a web2py que autentique a los usuarios usando una
cuenta del sistema operativo:
from gluon.contrib.login_methods.pam_auth import pam_auth
auth.settings.login_methods.append(pam_auth())

LDAP
La autenticacin utilizando LDAP funciona del mismo modo que en los casos anteriores.
Para usar el login de LDAP con MS Active Directory:
from gluon.contrib.login_methods.ldap_auth import ldap_auth
auth.settings.login_methods.append(ldap_auth(mode='ad',
server='mi.dominio.controlador',
base_dn='ou=Users,dc=domain,dc=com'))

Para usar el login de LDAP con Lotus Notes y Domino:


auth.settings.login_methods.append(ldap_auth(mode='domino',
server='mi.servidor.domino'))

Para usar el login de LDAP con OpenLDAP (con UID):


auth.settings.login_methods.append(ldap_auth(server='mi.servidor.ldap',
base_dn='ou=Users,dc=domain,dc=com'))

Para usar el login de LDAP con OpenLDAP (con CN):


auth.settings.login_methods.append(ldap_auth(mode='cn',
server='mi.servidor.ldap', base_dn='ou=Users,dc=domain,dc=com'))

Google App Engine


La autenticacin usando Google cuando se corre en Google App Engine requiere omitir el
formulario de acceso de web2py, redirigir a la pgina de acceso de Google y regresar en
caso de xito. Como el funcionamiento es distinto que en los ejemplos previos, la API es un
tanto diferente.
from gluon.contrib.login_methods.gae_google_login import GaeGoogleAccount
auth.settings.login_form = GaeGoogleAccount()

OpenID
Ya hemos tratado sobre la integracin con Janrain (que tiene soporte para OpenID) y
habamos notado que era la forma ms fcil de usar OpenID. Sin embargo, a veces no
deseas depender de un servicio de terceros y quieres acceder al proveedor de OpenID en
forma directa a travs de la app que consume el servicio, es decir, tu aplicacin.
Aqu se puede ver un ejemplo:
from gluon.contrib.login_methods.openid_auth import OpenIDAuth
auth.settings.login_form = OpenIDAuth(auth)
OpenIDAuth

requiere la instalacin del mdulo adicional python-openid. El mtodo define

automgicamente la siguiente tabla:


db.define_table('alt_logins',
Field('username', length=512, default=''),
Field('type', length =128, default='openid', readable=False),
Field('user', self.table_user, readable=False))

que almacena los nombres openid de cada usuario. Si quieres mostrar los openid de un
usuario autenticado:
{{=auth.settings.login_form.list_user_openids()}}

OAuth2.0 y Facebook
Hemos tratado previamente la integracin con Janrain (que tiene soporte para Facebook),
pero a veces no quieres depender de un servicio de terceros y deseas acceder al proveedor
de OAuth2.0 en forma directa; por ejemplo, Facebook. Esto se hace de la siguiente forma:

from gluon.contrib.login_methods.oauth20_account import OAuthAccount


auth.settings.login_form=OAuthAccount(TU_ID_DE_CLIENTE,TU_CLAVE_DE_CLIENTE)

Las cosas se tornan un tanto complicadas cuando quieres usar Facebook OAuth2.0 para
autenticar en una app especfica de Facebook para acceder a su API, en lugar de acceder a
tu propia app. Aqu mostramos un ejemplo para acceder a la Graph API de Facebook.
Antes que nada, debes instalar el Facebook Python SDK.
En segundo lugar, necesitas el siguiente cdigo en tu modelo:
# importar los mdulos requeridos
from facebook import GraphAPI
from gluon.contrib.login_methods.oauth20_account import OAuthAccount
# extensin de la clase OAUthAccount
class FaceBookAccount(OAuthAccount):
"""OAuth impl for Facebook"""
AUTH_URL="https://graph.facebook.com/oauth/authorize"
TOKEN_URL="https://graph.facebook.com/oauth/access_token"
def __init__(self, g):
OAuthAccount.__init__(self, g,
TU_ID_DE_CLIENTE,
TU_CLAVE_DE_CLIENTE,
self.URL_DE_AUTH,
self.PARAMETRO_URL) # token url
self.graph = None
# reemplazamos la funcin que recupera la informacin del usuario
def get_user(self):
"Devuelve el usuario de la Graph API"
if not self.accessToken():
return None
if not self.graph:

self.graph = GraphAPI((self.accessToken()))
try:
user = self.graph.get_object("me")
return dict(first_name = user['first_name'],
last_name = user['last_name'],
username = user['id'])
except GraphAPIError:
self.session.token = None
self.graph = None
return None
# puedes usar la clase definida arriba para crear
# un nuevo formulario de autenticacin
auth.settings.login_form=FaceBookAccount()

LinkedIn
Hemos tratado anteriormente sobre la integracin con Janrain (que tiene soporte para
LinkedIn) y esa es la forma ms sencilla para usar OAuth. Sin embargo, a veces no quieres
depender de un servicio de terceros o quieres acceder directamente a LinkedIn para
recuperar ms informacin de la que te provee Janrain.
He aqu un ejemplo:
from gluon.contrib.login_methods.linkedin_account import LinkedInAccount
auth.settings.login_form=LinkedInAccount(request,CLAVE,CLAVE_SECRET,URL_DE_RETORNO)
LinkedInAccount

requiere que se instale el mdulo adicional "python-linkedin".

X509
Adems puedes autenticar enviando a la pgina un certificado x509 y tu credencial se
extraer del certificado. Esto necesita instalado M2Crypto de esta direccin:
http://chandlerproject.org/bin/view/Projects/MeTooCrypto

Una vez que tienes M2Crypto instalado puedes hacer:

from gluon.contrib.login_methods.x509_auth import X509Account


auth.settings.actions_disabled=['register','change_password','request_reset_password']
auth.settings.login_form = X509Account()

Ahora puedes autenticar en web2py pasando tu certificado x509. La forma de hacerlo


depende del navegador, pero probablemente necesites usar certificados para webservices.
En ese caso puedes usar por ejemplo cURL para hacer pruebas a tu autenticacin:
curl -d "firstName=John&lastName=Smith" -G -v --key private.key \
--cert server.crt https://example/app/default/user/profile

Esto funciona instantneamente con Rocket (el servidor web incorporado) pero puedes
necesitar algunas configuraciones extra para que funcione del lado del servidor si vas a usar
un servidor diferente. En especial debes informar a tu servidor web dnde se ubican los
certificados en la mquina que aloja el sistema y que se requiere la verificacin de los
certificados que son enviados en forma remota. La forma de hacer esto depende del
servidor web y por lo tanto no lo trataremos aqu.
Mltiples formularios de acceso
Algunos mtodos de acceso hacen modificaciones al formulario de autenticacin, otros no.
Cuando lo hacen, es probable que no puedan coexistir. Esto a veces se puede resolver
especificando mltiples formularios de acceso en la misma pgina. web2py provee un
mtodo para esta tarea. Aqu se muestra un ejemplo mezclando el login normal (auth) y el
login RPX (janrain.com):
from gluon.contrib.login_methods.extended_login_form import ExtendedLoginForm
otro_formulario = RPXAccount(request, api_key='...', domain='...', url='...')
auth.settings.login_form = ExtendedLoginForm(auth, otro_formulario, signals=['token'])

Si se establecen las seales y uno de los parmetros en la solicitud coincide con alguna de
las seales, entonces realizar la llamada a otro_formulario.formulario_acceso como
alternativa.

otro_formulario

puede manejar algunas situaciones particulares, por ejemplo,

mltiples
pasos
del
lado
de otro_formulario.formulario_acceso .

del

acceso

con

En su defecto mostrar el formulario de acceso login normal junto con

OpenID

dentro

otro_formulario

Versiones de registros
Puedes usar Auth para habilitar el control de versiones de registros (record versioning):
db.enable_record_versioning(db,
archive_db=None,
archive_names='%(tablename)s_archive',
current_record='current_record'):

Esto le indica a web2py que cree una tabla de control de registros para cada tabla en

db

que almacene una copia de cada registro cuando se modifica. Lo que se almacena es la
copia antigua, no la nueva versin.
Los ltimos tres parmetros son opcionales:
archive_db

permite especificar otra base de datos donde se almacenarn

las tablas de control. Si se configura como None equivale a especificar db .


provee de un patrn para la nomenclatura utilizada para

archive_names

cada tabla.
current_record

especifica el nombre del campo tipo reference a usarse en

la tabla de control para referir al registro original sin modificar. Observa


que en caso de verificarse archive_db!=db entonces el campo de referencia
es meramente un campo tipo integer ya que no se contemplan las
referencias entre bases de datos.

Solo
se
realiza
campos modified_by y
Cuando
campo
que

habilitas
is_active

se

el

control
de
versiones
para
las
tablas
con
modified_on (como las creadas por ejemplo por auth.signature)

enable_record_versioning

si

los

registros

tienen

un

(tambin creado por auth.signature), los registros nunca se eliminarn sino

marcarn

con

is_active=False

De

hecho,

enable_record_versioning

agrega

un common_filter a cada tabla con control de versin que excluye los registros
con

is_active=False

Si habilitas

de manera que no sean visibles.

enable_record_versioning

no deberas usar

auth.archive

crud.archive

o de lo

contrario se producirn duplicados de registros. Esas funciones realizan la misma tarea

que

enable_record_versioning

pero en forma explcita y sern deprecadas, mientras

que

enable_record_versioning

lo hace automticamente.

Mail

Auth

Uno puede definir un motor de envo de correo con


from gluon.tools import Mail
mail = Mail()
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = '[email protected]'
mail.settings.login = 'usuario:contrasea'

o simplemente usar el mailer provisto con

auth

mail = auth.settings.mailer
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = '[email protected]'
mail.settings.login = 'usuario:contrasea'

Debes reemplazar los valores de mail.settings con los parmetros apropiados para tu
servidor SMTP. Establece mail.settings.login = None si el servidor de SMTP no requiere
autenticacin. Si no quieres utilizar TLS, establece mail.settings.tls = False
Puedes leer ms acerca de la API para email y su configuracin en el Captulo 8. Aqu nos
limitamos a tratar sobre la interaccin entre Mail y Auth .
En

Auth

, por defecto, la verificacin de correo electrnico est deshabilitada. Para

habilitarla, agrega las siguientes lneas en el modelo donde se define


auth.settings.registration_requires_verification = True
auth.settings.registration_requires_approval = False
auth.settings.reset_password_requires_verification = True
auth.messages.verify_email = 'Haz clic en el link http://' + \
request.env.http_host + \

auth

URL(r=request,c='default',f='user',args=['verify_email']) + \
'/%(key)s para verificar tu direccin de correo electrnico'
auth.messages.reset_password = 'Haz clic en el link http://' + \
request.env.http_host + \
URL(r=request,c='default',f='user',args=['reset_password']) + \
'/%(key)s para restablecer tu contrasea'

En los dos mensajes de

auth.messages

de arriba, podras necesitar reemplazar la parte del

URL en la cadena con la direccin absoluta y/o apropiada de la accin. Esto se debe a que
web2py podra estar instalado detrs de un proxy, y no puede determinar su propia URL
con absoluta certeza. Los ejemplos anteriores (que son los valores por defecto) deberan,
sin embargo, funcionar en la mayora de los casos.

Autorizacin
Una vez que se ha registrado un nuevo usuario, se crea un nuevo grupo que lo contiene. El
rol del nuevo usuario es por convencin "user_[id]" donde [id] es el id del nuevo usuario
creado. Se puede deshabilitar la creacin del grupo con
auth.settings.create_user_groups = None

aunque no es recomendable. Observa que


(aunque se puede establecer como

False

create_user_groups

no es un valor booleano

) sino que es por defecto:

auth.settings.create_user_groups="user_%(id)s"

Este almacena una plantilla para el nombre del grupo a crear para un determinado

id

de

usuario.
Los usuarios tienen membresa en los grupos. Cada grupo se identifica por un nombre/rol
o role. Los grupos tienen permisos. Los usuarios tienen permisos segn a qu grupos
pertenezcan. Por defecto cada usuario es nombrado miembro de su propio grupo.
Adems puedes hacer
auth.settings.everybody_group_id = 5

para que un usuario sea miembro automticamente del grupo nmero 5. Aqu 5 es utilizado
como ejemplo y asumimos que el grupo ya se ha creado de antemano.

Puedes crear grupos, asignar membresas y permisos a travs de appadmin o en forma


programtica usando los siguientes mtodos:
auth.add_group('rol', 'descripcin')

el mtodo devuelve el id del nuevo grupo creado.


auth.del_group(id_grupo)

borra el grupo que tenga id

id_grupo

auth.del_group(auth.id_group('user_7'))

borra el grupo cuyo rol es "user_7", es decir, el grupo exclusivo del usuario nmero 7
auth.user_group(id_usuario)

devuelve el id del grupo exclusivo del usuario identificado con el id

id_usuario

auth.add_membership(id_grupo, id_usuario)

le otorga membresa en el grupo

id_grupo

al usuario

id_usuario

. Si el usuario no se

especifica, web2py asume que se trata del usuario autenticado.


auth.del_membership(id_grupo, id_usuario)

expulsa al usuario

id_usuario

del grupo

id_grupo

. Si no se especifica el usuario, entonces

web2py asume que se trata del usuario autenticado.


auth.has_membership(id_grupo, id_usuario, rol)

verifica que el usuario

id_usuario

es miembro del grupo

especificado. Solo se debera pasar


especifica

id_usuario

id_grupo

id_grupo

o el grupo con el rol

o rol a la funcin, no ambos. Si no se

, entonces web2py asume que se trata del usuario autenticado.

auth.add_permission(id_grupo, 'nombre', 'objeto', id_registro)

otorga permiso para "nombre" (definido por el usuario) sobre "objeto" (tambin definido
por el usuario) a miembros del grupo id_grupo . Si "objeto" es un nombre de tabla entonces
el permiso puede hacer referencia a toda la tabla estableciendo el valor de
cero

bien

el

permiso

puede

hacer

referencia

un

id_registro

registro

como

especfico

estableciendo

id_registro

con un valor numrico mayor a cero. Cuando se otorgan permisos

sobre tablas, es una prctica comn la utilizacin de nombres comprendidos en el conjunto


('create', 'read', 'update', 'delete', 'select'). Estos parmetros son tratados especialmente y
controlados en forma instantnea por las API para CRUD.
Si el valor

id_grupo

es cero, web2py usa el grupo exclusivo para el usuario actualmente

autenticado.
Tambin puedes usar

auth.id_group(role="...")

para recuperar el id del grupo por su nombre.

auth.del_permission(group_id, 'nombre', 'objeto', id_registro)

anula el permiso.
auth.has_permission('nombre', 'objeto', id_registro, id_usuario)

comprueba que el usuario identificado por

id_usuario

tiene membresa en un grupo con el

permiso consultado.
registro = db(auth.accessible_query('read', db.mitabla, id_usuario))\
.select(db.mitabla.ALL)

devuelve todo registro de la tabla "mitabla" para el cual el usuario

id_usuario

tiene permiso

de lectura. Si el usuario no se especifica, entonces web2py asume que se trata del usuario
autenticado. El comando accessible_query(...) se puede combinar con otras consultas para
obtener consultas ms complicadas.

accessible_query(...)

es el nico mtodo de Auth que

requiere el uso de JOIN, por lo que no es utilizable en Google App Engine.


Suponiendo que se han establecido las siguientes definiciones:
>>> from gluon.tools import Auth
>>> auth = Auth(db)
>>> auth.define_tables()
>>> secretos = db.define_table('documento', Field('cuerpo'))
>>> james_bond = db.auth_user.insert(first_name='James',
last_name='Bond')

He aqu un ejemplo:

>>> doc_id = db.documento.insert(cuerpo = 'confidencial')


>>> agentes = auth.add_group(role = 'Agente secreto')
>>> auth.add_membership(agentes, james_bond)
>>> auth.add_permission(agentes, 'read', secretos)
>>> print auth.has_permission('read', secretos, doc_id, james_bond)
True
>>> print auth.has_permission('update', secretos, doc_id, james_bond)
False

Decoradores
La forma ms corriente de comprobar credenciales no es usar llamadas explcitas a los
mtodos descriptos ms arriba, sino decorando las funciones para que se compruebe la
permisologa en funcin del usuario autenticado. Aqu se muestran algunos ejemplos:
def funcion_uno():
return 'esta es una funcin pblica'

@auth.requires_login()
def funcion_dos():
return 'esta requiere de acceso'

@auth.requires_membership('agentes')
def funcion_tres():
return 'eres un agente secreto'

@auth.requires_permission('read', secretos)
def funcion_cuatro():
return 'tienes permiso para leer los documentos secretos'

@auth.requires_permission('delete', 'archivos todos')


def funcion_cinco():

import os
for file in os.listdir('./'):
os.unlink(file)
return 'se borraron todos los archivos'

@auth.requires(auth.user_id==1 or request.client=='127.0.0.1', requires_login=True)


def funcion_seis():
return 'puedes leer documentos secretos'

@auth.requires_permission('sumar', 'nmero')
def sumar(a, b):
return a + b

def funcion_siete():
return sumar(3, 4)

El argumento que especifica los requisitos de

auth.requires(condicin)

puede ser un

objeto callable y a menos que la condicin sea simple, es preferible pasar un callable que
una condicin porque ser ms rpido, ya que la condicin slo se evaluar en caso de ser
necesario. Por ejemplo
@auth.requires(lambda: comprobar_condicion())
def accion():
....
@auth.requires

defecto

True

tambin

toma

un

argumento

opcional

requires_login

que

es

por

. Si se establece como False, no requiere login antes de evaluar la condicin de

verdadero/falso. La condicin puede ser un valor booleano o una funcin que evale a un
booleano.
Observa que el acceso a todas las funciones excepto la de la primera y la ltima est
restringido segn la permisologa asociada al usuario que realiz la solicitud.

Si no hay un usuario autenticado, entonces los permisos no se pueden comprobar; el


visitante es redirigido a la pgina de login y luego de regreso a la pgina que requiere los
permisos.

Combinando requisitos
Ocasionalmente, hace falta combinar requisitos. Esto puede hacerse a travs de un
decorador con requires genrico que tome un nico argumento que consista en una
condicin verdadera o falsa. Por ejemplo, para dar acceso a los agentes, pero solo los das
martes:
@auth.requires(auth.has_membership(group_id='agentes' \
and request.now.weekday()==1)
def funcion_siete():
return 'Hola agente, debe ser martes!'

o el equivalente:
@auth.requires(auth.has_membership(role='Agente secreto') \
and request.now.weekday()==1)
def funcion_siete():
return 'Hola agente, debe ser martes!'

CRUD y Autorizacin
El uso de decoradores o comprobaciones explcitas proveen de una forma de
implementacin para el control de acceso.
Otra forma de implementacin del control de acceso es siempre usar CRUD (en lugar
de SQLFORM ) para acceder a la base de datos e indicarle a CRUD que debe controlar el
acceso a las tablas y registros de la base de datos. Esto puede hacerse
enlazando Auth y CRUD con la siguiente instruccin:
crud.settings.auth = auth

Esto evitar que el visitante acceda a cualquier operacin CRUD a menos que est
autenticado y tenga la permisologa adecuada. Por ejemplo, para permitir a un visitante que
publique comentarios, pero que slo pueda actualizar sus comentarios (suponiendo que se
han definido crud, auth y db.comentario):

def otorgar_permiso_crear(formulario):
id_grupo = auth.id_group('user_%s' % auth.user.id)
auth.add_permission(id_grupo, 'read', db.comentario)
auth.add_permission(id_grupo, 'create', db.comentario)
auth.add_permission(id_grupo, 'select', db.comentario)

def otorgar_permiso_actualizar(formulario):
id_comentario = formulario.vars.id
id_grupo = auth.id_group('user_%s' % auth.user.id)
auth.add_permission(group_id, 'update', db.comentario, id_comentario)
auth.add_permission(group_id, 'delete', db.comentario, id_comentario)

auth.settings.register_onaccept = otorgar_permiso_crear
crud.settings.auth = auth

def publicar_comentario():
formulario = crud.create(db.comentario, onaccept=otorgar_permiso_actualizar)
comentarios = db(db.comentario).select()
return dict(formulario=formulario, comentarios=comentarios)

def actualizar_comentario():
formulario = crud.update(db.comentario, request.args(0))
return dict(formulario=formulario)

Adems puedes recuperar registros especficos (aquellos para los que est habilitada la
lectura 'read'):
def publicar_comentario():
formulario = crud.create(db.comentario, onaccept=otorgar_permiso_actualizar)
consulta = auth.accessible_query('read', db.comentario, auth.user.id)

comentarios = db(consulta).select(db.comentario.ALL)
return dict(formulario=formulario, comentarios=comentarios)

Los nombres empleados para la permisologa manejados por:


crud.settings.auth = auth

son "read", "create", "update", "delete", "select", "impersonate".

Autorizacin y descargas
El uso de decoradores y de

crud.settings.auth

no establece un control de acceso a archivos

descargados con la funcin de descarga corriente.


def download(): return response.download(request, db)

En caso de ser necesario, uno debe declarar explcitamente cules campos tipo "upload"
contienen archivos que requieren control de acceso al descargarse. Por ejemplo:
db.define_table('perro',
Field('miniatura', 'upload'),
Field('imagen', 'upload'))

db.perro.imagen.authorization = lambda registro: \


auth.is_logged_in() and \
auth.has_permission('read', db.perro, registro.id, auth.user.id)

El atributo

authorization

del campo upload puede ser None (por defecto) o una funcin

personalizada que compruebe credenciales y/o la permisologa para los datos consultados.
En el caso del ejemplo, la funcin comprueba que usuario est autenticado y tenga permiso
de lectura para el registro actual. Adems, tambin para este caso particular, no existe una
restriccin sobre la descarga de imgenes asociadas el campo "miniatura", pero requiere
control de acceso para las imgenes asociadas al campo "imagen".

Control de Acceso y Autenticacin Bsica


En algunas ocasiones, puede ser necesario exponer acciones con decoradores que
implementan control de acceso como servicios; por ejemplo, cuando se los llama desde un

script o programa pero con la posibilidad de utilizar el servicio de autenticacin para


comprobar las credenciales de acceso.
Auth contempla el acceso por el mtodo de autenticacin bsico:
auth.settings.allow_basic_login = True

Con esa opcin, una accin como por ejemplo


@auth.requires_login()
def la_hora():
import time
return time.ctime()

puede invocarse, por ejemplo, desde un comando de la consola:


wget --user=[usuario] --password=[contrasea]
http://.../[app]/[controlador]/la_hora

Tambin es posible dar acceso llamando a


decorador

@auth

auth.basic()

en lugar de usar un

def la_hora():
import time
auth.basic()
if auth.user:
return time.ctime()
else:
return 'No tiene autorizacin'

El mtodo de acceso bsico es a menudo la nica opcin para servicios (tratados en el


prximo captulo), pero no est habilitado por defecto.

Autenticacin manual
A veces necesitas implementar tus propios algoritmos y hacer un sistema de acceso
"manual". Esto tambin est contemplado llamando a la siguiente funcin:

user = auth.login_bare(usuario, contrasea)


login_bare

devuelve el objeto user en caso de que exista y su contrasea es vlida, de lo

contrario devuelve False.


tabla

auth_user

username

no tiene un campo

es la direccin de correo electrnico si la

username

Configuraciones y mensajes
Esta es la lista de todos los parmetros que se pueden personalizar para Auth
Para

que

objeto

gluon.toools.Mail

auth

pueda

enviar

correos

se

debe

enlazar

lo

siguiente

user

un

auth.settings.mailer = None

El siguiente debe ser el nombre del controlador que define la accin


auth.settings.controller = 'default'

El que sigue es un parmetro muy importante:


auth.settings.hmac_key = None

Debe tomar un valor similar a "sha512:una-frase-de-acceso" y se pasar como parmetro al


validador CRYPT para el campo "password" de la tabla auth_user . Sern el algoritmo y la
frase de acceso usados para hacer un hash de las contraseas.
Por defecto, auth tambin requiere una extensin mnima para las contraseas de 4
caracteres. Esto se puede modificar:
auth.settings.password_min_length = 4

Para deshabilitar una accin agrega su nombre a la siguiente lista:


auth.settings.actions_disabled = []

Por ejemplo:
auth.settings.actions_disabled.append('register')

deshabilitar el registro de usuarios.

Si deseas recibir un correo para verificar el registro de usuario debes configurar este
parmetro como True :
auth.settings.registration_requires_verification = False

Para autenticar automticamente a los usuarios una vez que se hayan registrado, incluso si
no han completado el proceso de verificacin del correo electrnico, establece el siguiente
parmetro como True :
auth.settings.login_after_registration = False

Si los nuevos usuarios registrados deben esperar por la aprobacin antes de poder acceder
configura esto como True :
auth.settings.registration_requires_approval = False

La aprobacin consiste en establecer el valor

registration_key==''

a travs de appadmin o

programticamente.
Si no deseas que se genere un nuevo grupo para cada usuario establece el siguiente
parmetro como False :
auth.settings.create_user_groups = True

Las siguientes configuraciones establecen mtodos alternativos para el acceso o login,


tratados ms arriba:
auth.settings.login_methods = [auth]
auth.settings.login_form = auth

Necesitas habilitar el acceso bsico?


auth.settings.allows_basic_login = False

El siguiente URL corresponde a la accin de autenticacin login:


auth.settings.login_url = URL('user', args='login')

Si el usuario intenta acceder a la pgina de registro pero ya se ha autenticado, se lo


redirigir a esta URL:
auth.settings.logged_url = URL('user', args='profile')

Esto debe referir al URL de la accin download, en caso de que el perfil contenga
imgenes:
auth.settings.download_url = URL('download')

Estos parmetros deben enlazar al URL al que quieras usar para redirigir a tus usuarios
luego de cada accin de tipo auth (en caso de que no se haya establecido un parmetro de
redireccin especial o referrer):
auth.settings.login_next = URL('index')
auth.settings.logout_next = URL('index')
auth.settings.profile_next = URL('index')
auth.settings.register_next = URL('user', args='login')
auth.settings.retrieve_username_next = URL('index')
auth.settings.retrieve_password_next = URL('index')
auth.settings.change_password_next = URL('index')
auth.settings.request_reset_password_next = URL('user', args='login')
auth.settings.reset_password_next = URL('user', args='login')
auth.settings.verify_email_next = URL('user', args='login')

Si el usuario no se ha autenticado, y ejecuta una funcin que requere autenticacin,


entonces ser redirigido a auth.settings.login_url que por defecto es URL('default',
'user/login')

. Podemos cambiar ese comportamiento si redefinimos:

auth.settings.on_failed_authentication = lambda url: redirect(url)

Que es la funcin a la que se llama para las redirecciones. El argumento

url

pasado a esta

funcin es el url para la pgina de acceso (login page).


Si el visitante no tiene permiso de acceso a una funcin determinada, es redirigido al URL
definido por
auth.settings.on_failed_authorization = \
URL('user',args='on_failed_authorization')

Puedes cambiar esa variable y redirigir al usuario a otra parte.

A menudo querrs usar

on_failed_authorization

como URL pero puede tomar como

parmetro una funcin que devuelva el URL y que ser llamada en caso de fallar la
autorizacin.
Hay listas de llamadas de retorno que deberan ejecutarse luego de la validacin de
formularios para cada una de las acciones correspondientes y antes de toda E/S de la base
de datos:
auth.settings.login_onvalidation = []
auth.settings.register_onvalidation = []
auth.settings.profile_onvalidation = []
auth.settings.retrieve_password_onvalidation = []
auth.settings.reset_password_onvalidation = []

Cada llamada de retorno puede ser una funcin que toma un objeto

form

y puede modificar

los atributos de ese formulario antes de aplicarse los cambios en la base de datos.
Hay listas de llamadas de retorno o callback que se deberan ejecutar luego de la E/S de la
base de datos y antes de la redireccin:
auth.settings.login_onaccept = []
auth.settings.register_onaccept = []
auth.settings.profile_onaccept = []
auth.settings.verify_email_onaccept = []

He aqu un ejemplo:
auth.settings.register_onaccept.append(lambda formulario:\
mail.send(to='[email protected]',subject='nuevo usuario',
message='el email del nuevo usuario es %s'%formulario.vars.email))

Puedes habilitar captcha para cualquiera de las acciones de


auth.settings.captcha = None
auth.settings.login_captcha = None
auth.settings.register_captcha = None

auth

auth.settings.retrieve_username_captcha = None
auth.settings.retrieve_password_captcha = None

Si los parmetros de

.captcha

hacen referencia a

gluon.tools.Recaptcha

formularios para los cuales la opcin correspondiente (como


establecido como

None

.captcha

como

.login_captcha

) se haya

tendrn captcha, mientras que aquellos para los que la opcin

correspondiente se ha establecido como


establece

, todos los

None

correspondiente con un objeto

False

no lo tendrn. Si, en cambio, se

, solo aquellos formularios que tengan la opcin


gluon.tools.Recaptcha

como parmetro, tendrn captcha, los

otros, no.
Este es el tiempo de vencimiento de la sesin:
auth.settings.expiration = 3600 # segundos

Puedes cambiar el nombre del campo para la contrasea (en Firebird, por ejemplo,
"password" es una palabra especial y no se puede usar para nombrar un campo):
auth.settings.password_field = 'password'

Normalmente el formulario de acceso intenta verificar el formato de los correos. Esto se


puede deshabilitar modificando la configuracin:
auth.settings.login_email_validate = True

Quieres mostrar el id de registro en la pgina de edicin del perfil?


auth.settings.showid = False

Para formularios personalizados puedes necesitar que las notificaciones automticas de


errores en formularios estn deshabilitadas:
auth.settings.hideerror = False

Adems para formularios personalizados puedes cambiar el estilo:


auth.settings.formstyle = 'table3cols'

(puede ser "table2cols", "divs" y "ul")


Y puedes especificar un separador para los formularios generados por auth:

auth.settings.label_separator = ':'

Por defecto, el formulario de autenticacin da la opcin de extender el acceso con una


opcin "remember me". El plazo de vencimiento se puede cambiar o deshabilitar la opcin
con estos parmetros:
auth.settings.long_expiration = 3600*24*30 # un mes
auth.settings.remember_me_form = True

Tambin puedes personalizar los siguientes mensajes cuyo uso y contexto deberan ser
obvios:
auth.messages.submit_button = 'Enviar'
auth.messages.verify_password = 'Verificar contrasea'
auth.messages.delete_label = 'Marque para eliminar:'
auth.messages.function_disabled = 'Funcin deshabilitada'
auth.messages.access_denied = 'Privilegios insuficientes'
auth.messages.registration_verifying = 'El registro de usuario requiere verificacin'
auth.messages.registration_pending = 'El registro de usuario est pendiente de aprobacin'
auth.messages.login_disabled = 'El acceso fue deshabilitado por el administrador'
auth.messages.logged_in = 'Autenticado'
auth.messages.email_sent = 'Correo enviado'
auth.messages.unable_to_send_email = 'Fall el envo del correo'
auth.messages.email_verified = 'Direccin de correo verificada'
auth.messages.logged_out = 'Se ha cerrado la sesin'
auth.messages.registration_successful = 'Registro de usuario completado'
auth.messages.invalid_email = 'Direccin de correo invlida'
auth.messages.unable_send_email = 'Fall el envo del correo'
auth.messages.invalid_login = 'Fall la autenticacin'
auth.messages.invalid_user = 'El usuario especificado no es vlido'
auth.messages.is_empty = "No puede ser vaco"
auth.messages.mismatched_password = "Los campos de contrasea no coinciden"

auth.messages.verify_email = ...
auth.messages.verify_email_subject = 'Verificacin de contrasea'
auth.messages.username_sent = 'Su nombre de usuario ha sido enviado por correo'
auth.messages.new_password_sent = 'Se ha enviado una nueva contrasea a su correo'
auth.messages.password_changed = 'Contrasea modificada'
auth.messages.retrieve_username = 'Su nombre de usuario es: %(username)s'
auth.messages.retrieve_username_subject = 'Recuperar usuario'
auth.messages.retrieve_password = 'Su contrasea de usuario es: %(password)s'
auth.messages.retrieve_password_subject = 'Recuperar contrasea'
auth.messages.reset_password = ...
auth.messages.reset_password_subject = 'Restablecer contrasea'
auth.messages.invalid_reset_password = 'Nueva contrasea invlida'
auth.messages.profile_updated = 'Perfil actualizado'
auth.messages.new_password = 'Nueva contrasea'
auth.messages.old_password = 'Vieja contrasea'
auth.messages.group_description = \
'Grupo exclusivo del usuario %(id)s'
auth.messages.register_log = 'Usuario %(id)s registrado'
auth.messages.login_log = 'Usuario %(id)s autenticado'
auth.messages.logout_log = 'Usuario %(id)s cerr la sesin'
auth.messages.profile_log = 'Usuario %(id)s perfil actualizado'
auth.messages.verify_email_log = 'Usuario %(id)s correo de verificacin enviado'
auth.messages.retrieve_username_log = 'Usuario %(id)s nombre de usuario recuperado'
auth.messages.retrieve_password_log = 'Usuario %(id)s contrasea recuperada'
auth.messages.reset_password_log = 'Usuario %(id)s contrasea restablecida'
auth.messages.change_password_log = 'Usuario %(id)s se cambi la contrasea'
auth.messages.add_group_log = 'Grupo %(group_id)s creado'
auth.messages.del_group_log = 'Grupo %(group_id)s eliminado'
auth.messages.add_membership_log = None

auth.messages.del_membership_log = None
auth.messages.has_membership_log = None
auth.messages.add_permission_log = None
auth.messages.del_permission_log = None
auth.messages.has_permission_log = None
auth.messages.label_first_name = 'Nombre'
auth.messages.label_last_name = 'Apellido'
auth.messages.label_username = 'Nombre de Usuario'
auth.messages.label_email = 'Correo Electrnico'
auth.messages.label_password = 'Contrasea'
auth.messages.label_registration_key = 'Clave de registro de usuario'
auth.messages.label_reset_password_key = 'Clave para restablecer contrasea'
auth.messages.label_registration_id = 'Identificador del registro de usuario'
auth.messages.label_role = 'Rol'
auth.messages.label_description = 'Descripcin'
auth.messages.label_user_id = 'ID del Usuario'
auth.messages.label_group_id = 'ID del Grupo'
auth.messages.label_name = 'Nombre'
auth.messages.label_table_name = 'Nombre de Tabla'
auth.messages.label_record_id = 'ID del Registro'
auth.messages.label_time_stamp = 'Fecha y Hora'
auth.messages.label_client_ip = 'IP del Cliente'
auth.messages.label_origin = 'Origen'
auth.messages.label_remember_me = "Recordarme (por 30 das)"

Los registros de membresa

add|del|has

permiten le uso de "%(user_id)s" y "%

(group_id)s". Los registros de permisos add|del|has permiten el uso de "%(user_id)s", "%


(name)s", "%(table_name)s", y "%(record_id)s".

Servicio Central de Autenticacin

web2py provee de soporte para la autenticacin con servicios de terceros y single sign on.
Aqu describimos el Servicio Central de Autenticacin (CAS, Central Authentication
Service) que es un estndar industrial y tanto el cliente como el servidor estn incorporados
en web2py.
CAS es un protocolo abierto para la autenticacin distribuida y funciona de la siguiente
forma: Cuando un visitante arriba a nuestro sitio web, nuestra aplicacin comprueba en la
sesin si el usuario ya est autenticado (por ejemplo a travs de un objeto session.token ).
Si el usuario no se ha autenticado, el controlador redirige al visitante desde la aplicacin de
CAS, donde puede autenticarse, registrarse y manejar sus credenciales (nombre, correo
electrnico, contrasea). Si el usuario se registra, recibir un correo; el registro de usuario
no estar completo hasta que el usuario conteste el correo. Una vez que el usuario est
exitosamente registrado y autenticado, la aplicacin CAS redirige al usuario a nuestra
aplicacin junto con una clave. Nuestra aplicacin utiliza la clave para obtener las
credenciales del usuario a travs de una solicitud HTTP en segundo plano al servidor CAS.
Usando este mecanismo, mltiples aplicaciones pueden utilizar un sistema single sing-on a
travs del servicio CAS. El servidor que provee la autenticacin es denominado proveedor
del servicio. Aquellas aplicaciones que requieren la autenticacin de los visitantes se
llaman consumidores del servicio.
CAS es similar a OpenID, con una diferencia esencial. En el caso de OpnenID, el visitante
elige el proveedor del servicio. En el caso de CAS, nuestra aplicacin hace esa eleccin.
Haciendo que CAS sea ms segura.
Corriendo un proveedor CAS con web2py es tan fcil como copiar la app de andamiaje. De
hecho cualquier app que exponga la accin
## proveedor de acceso
def user(): return dict(form=auth())

es un proveedor de CAS 2.0 y se puede acceder a sus servicios con los URL
http://.../proveedor/default/user/cas/login
http://.../proveedor/default/user/cas/validate
http://.../proveedor/default/user/cas/logout

(suponemos que la app se llama "proveedor").

Puedes acceder a este servicio desde cualquier otra aplicacin web (el consumidor)
simplemente relegando la autenticacin al proveedor:
## en la app consumidor
auth = Auth(db,cas_provider = 'http://127.0.0.1:8000/proveedor/default/user/cas')

Cuando visitas el url de acceso de la app consumidor, te redirigir a la app proveedor, que
realizar la autenticacin y luego redirigir nuevamente a la app consumidor. Todos los
procesos de registro de usuarios, cerrar sesin, cambio de contrasea o recuperar
contrasea, se deben completar en la app proveedor. Se crear un registro sobre el acceso
del usuario del lado del consumidor para que se puedan agregar campos extra y un perfil
local. Gracias a CAS 2.0 todos los campos accesibles para lectura en el proveedor que
tienen su correspondiente campo en la tabla auth_user del consumidor se copiarn
automticamente.
Auth(..., cas_provider='...')

funcional con proveedores de terceros y soporta CAS 1.0 y 2.0.

La versin se detecta automticamente. Por defecto genera los URL del proveedor a partir
de una base (el url de cas_provider de arriba) agregando
/login
/validate
/logout

Estos valores se pueden cambiar tanto en el consumidor como en el proveedor


## en la app del consumidor y del proveedor (deben coincidir)
auth.settings.cas_actions['login']='login'
auth.settings.cas_actions['validate']='validate'
auth.settings.cas_actions['logout']='logout'

Si deseas conectar a un proveedor CAS de web2py desde un dominio diferente, debes


habilitarlos agregndolos a la lista de dominios autorizados:
## en la app proveedor
auth.settings.cas_domains.append('example.com')

Uso de web2py para autenticar otras aplicaciones


Esto es posible pero depende del servidor web. aqu vamos a suponer que las aplicaciones
corren en el mismo servidor web: Apache con mod_wsgi . Una de las aplicaciones es
web2py con una app que provee control de acceso por medio de Auth. La otra aplicacin
puede ser un script CGI, un programa en PHP o cualquier otra cosa. Queremos que el
servidor web solicite permisos a la primera aplicacin cuando una solicitud de un cliente
accede a la segunda.
En primer lugar es necesario modificar la aplicacin de web2py y agregar el siguiente
controlador:
def verificar_acceso():
return 'true' if auth.is_logged_in() else 'false'

que devuelve

true

si el usuario se autentic y

false

en su defecto. Ahora ejecutemos un

proceso en segundo plano:


nohup python web2py.py -a '' -p 8002

El puerto 8002 es indispensable y no hay necesidad de habilitar admin, por lo que no se


especifica una contrasea.
Luego debemos editar el archivo de configuracin de Apache (por ejemplo
"/etc/apache2/sites-available/default") para que cuando una app que no sea de web2py
reciba una solicitud, llame a la funcin para verificar_acceso definida ms arriba en su
lugar y, si esta devuelve

true

, que contine con la respuesta a la solicitud, o de lo contrario

que prohba el acceso.


Como web2py y la otra aplicacin corren en el mismo dominio, si el usuario est
autenticado en la app de web2py, la cookie de la sesin ser enviada al servidor web
Apache incluso si la solicitud es para la otra aplicacin y admitir la verificacin de las
credenciales.
Para que esto sea posible necesitamos un script, "web2py/scripts/access.wsgi" que sabe
como lidiar con ese asunto. El script viene incluido con web2py. Todo lo que tenemos que
hacer es decirle a apache que llame al script, e informar el URL de la aplicacin que
requiere control de acceso y la ubicacin del script:
<VirtualHost *:80>

WSGIDaemonProcess web2py user=www-data group=www-data


WSGIProcessGroup web2py
WSGIScriptAlias / /home/www-data/web2py/wsgihandler.py

AliasMatch ^ruta/a/miapp/que/requiere/autenticacion/miarchivo /ruta/al/archivo


<Directory /ruta/a/>
WSGIAccessScript /ruta/a/web2py/scripts/access.wsgi
</Directory>
</VirtualHost>

Aqu "^ruta/a/miapp/que/requiere/autenticacion/miarchivo" es la expresin regular que


debera coincidir con la solicitud entrante y "/ruta/a" es la ubicacin absoluta de la carpeta
de web2py.
El script "access.wsgi" contiene la siguiente lnea:
URL_CHECK_ACCESS = 'http://127.0.0.1:8002/%(app)s/default/check_access'

que refiere a la aplicacin de web2py que hemos solicitado pero puedes editarlo para que
refiera a una aplicacin especfica, que corra en otro puerto que no sea 8002.
Adems puedes cambiar la accin

check_access()

y hacer que su algoritmo sea ms

complejo. Esta accin puede recuperar el URL que fue originalmente solicitado usando la
variable de entorno
request.env.request_uri

y puedes implementar reglas ms complejas:


def verificar_acceso():
if not auth.is_logged_in():
return 'false'
elif not usuario_tiene_acceso(request.env.request_uri):
return 'false'
else:
return 'true'

También podría gustarte