Web 2 Py
Web 2 Py
Web 2 Py
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.
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()
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).
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.
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
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:
En el diagrama:
o
"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.
o
o
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
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.
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
etc. web2py, por defecto, "escapa" todas las variables procesadas en la vista, previniendo
el XSS.
o
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.
El paquete y su contenido
Puedes descargar web2py desde el sitio oficial:
http://www.web2py.com
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:
Python que se usan en el resto del libro. Si ya conoces Python, puedes omitir este
captulo.
o
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 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 ).
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.
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.
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
[guido]
y ref.[lutz].
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:
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
...
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
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;
[pydocs]
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
y concatenar:
>>> a = [2, 3]
>>> b = [5, 6]
>>> print a + b
[2, 3, 5, 6]
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)
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
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
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
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
if...elif...else
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):
>>>
try...except...else...finally
a=1/0
>>> else:
>>>
>>> finally:
>>>
print 'listo'
raise SyntaxError
>>>
error sintctico
+-- ArithmeticError
+-- FloatingPointError
+-- OverflowError
+-- ZeroDivisionError
+-- AssertionError
+-- AttributeError
+-- EnvironmentError
+-- IOError
+-- OSError
+-- 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
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
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
lambda
lambda presenta una forma de declarar en forma fcil y abreviada funciones sin nombre:
>>> a = lambda b: b + 2
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]
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
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
>>>
>>>
self.x = a
>>>
self.y = b
>>>
>>>
def sumar(self):
return self.x + self.y + self.z
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.
__len__
__getitem__
__setitem__
Se pueden usar, por ejemplo, para crear un objeto contenedor que se comporta como una
lista:
>>>
>>>
>>>
>>> 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()
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
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'
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
principalmente
definidos
en
mdulos
Este mdulo provee de una interfaz a la API del sistema operativo. Por ejemplo:
>>> import os
>>> os.chdir('..')
>>> os.unlink('archivo_a_borrar')
ruta/sub_ruta
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')
datetime
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
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
y ahora:
>>> import cPickle
>>> b = cPickle.dumps(a)
>>> c = cPickle.loads(b)
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
web2py.exe
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/
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 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
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.
o
o
imgenes
estticas,
archivos
CSS
[css-w,css-o,css-school]
archivos
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"
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:
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]
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:
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>
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.
def segunda():
return dict()
{{extend 'layout.html'}}
<h1>Cul es tu nombre?</h1>
<form action="segunda">
<input name="nombre_del_visitante" />
<input type="submit" />
</form>
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:
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".
def segunda():
return dict()
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)
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.
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":
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'))
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')
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
"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:
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
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.
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
como
diccionarios: imagenes[0]
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>
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)
se convierte en:
<li><a href="/imagenes/default/mostrar/123">algo</a></li>
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:
y esto resulta en un error, ya que todava no has creado una accin llamada "mostrar" en el
controlador "default.py".
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)
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'))
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.
<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}}
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)
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():
...
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.
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
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')
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),
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
mostrar: mostrar una pgina wiki, listar sus comentarios y agregar comentarios nuevos
callback: una funcin callback de Ajax. Devuelve el HTML que se embebe en la pgina
de bsqueda mientras el visitante escribe.
@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}}
{{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}}
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:
Si, desde la pgina "mostrar", haces clic en documentos, ahora puedes administrar los
documentos asociados a la pgina.
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
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])
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.
Cuando las fechas se convierten a cadenas en los formularios son convertidas utilizando la
representacin ISO
%Y-%m-%d %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']
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
un
diccionario
con
una
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/...
render , que es por defecto 'markmin' pero que puede establecerse como 'html' .
manage_permissions . Esta opcin tiene el valor False por defecto y slo reconocer la
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.
algunos
parmetros
adicionales
que
se
explicarn
ms
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
Por ejemplo:
auth.wiki(env=dict(unir=lambda a, b, c:"%s-%s-%s" % (a, b, c)))
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 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 .
La
implementacin
pertenece
al
archivo
de
web2py gluon.contrib.autolinks ms
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.
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.
o la nube de etiquetas:
{{=auth.wiki('_cloud')}}
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)
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}
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
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.
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)
Desinstalar la aplicacin
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.
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.
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.
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 test (pruebas), web2py correr los tests. Los tests son creados por el
desarrollador utilizando doctests, y cada funcin debera tener sus propios tests.
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
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)
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.
<h1>Imgenes registradas</h1>
<ul>
{{for imagen in imagenes:}}
{{1/0}}
{{=LI(A(imagen.titulo, _href=URL("mostrar", args=imagen.id)))}}
{{pass}}
</ul>
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).
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:
El asistente te guiar a travs de una serie de pasos para la creacin de una nueva aplicacin:
o
Armar los modelos requeridos (crear pginas de ABM/CRUD para cada modelo)
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
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
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
Opciones:
--version
-h, --help
-i IP, --ip=IP
-D NIVEL_DEPURACIN, --debug=NIVEL_DEPURACIN
establece el nivel de la salida de depuracin
-P, --plain
--softcron
-Y, --run-cron
-J, --cronjob
-L CONFIG, --config=CONFIG
archivo de configuracin
-F ARCHIVO_PROFILER, --profiler=ARCHIVO_PROFILER
nombre de archivo del profiler
-t, --taskbar
--nogui
--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
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
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.
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.
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).
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.
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.
applications/a/models/*.py
applications/a/models/c/*.py
applications/a/models/c/f/*.py
o
o
(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)
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
y:
request.vars = {'p':1, 'q':2}
y tambin:
request.application = 'a'
request.controller = 'c'
request.function = 'f'
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
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
Guarda la sesin.
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
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/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/myregex.py
gluon/newcron.py
gluon/settings.py
gluon/shell.py
gluon/sql.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
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
# no se recomienda
gaehandler.py
fcgihandler.py
# para FastCGI
wsgihandler.py
# para WSGI
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
markmin markup:
gluon/contrib/markmin
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
redis_cache
es un mdulo para el almacenamiento de cach en la base de datos redis:
gluon/contrib/redis_cache.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
"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
Un clasificador bayesiano para crear registros ficticios de la base de datos utilizados para
pruebas:
gluon/contrib/populate.py
Un archivo que permite la interaccin con la barra de tareas de Windows, cuando web2py
corre como servicio:
gluon/contrib/taskbar_widget.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
static files los archivos estticos no requieren procesamiento (por ejemplo imgenes,
hojas de estilo CSS, etc).
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).
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
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
estn
definidos
en
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.
cambiar.
Para
habilitar
la
recarga
automtica
de
mdulos,
utiliza
funcin track_changes como sigue (tpicamente en un mdulo, antes de cualquier import):
la
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
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
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
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
es lo mismo que:
request['vars']
la clase Storage :
o
con la solicitud HTTP. Se comporta como un diccionario compuesto por cookie. Cada
cookie es un objeto Morsel[morsel].
o
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
solicitud actual.
o
solicitud actual.
request.args : Una lista de los componentes de la ruta de la URL que siguen despus
request.client :
La
direccin
ip
del
cliente
determinada
por,
si
se
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.cid es el id del componente que gener la solicitud Ajax (en caso de existir).
request.restful este es un decorador nuevo y realmente til que se puede usar para
request.wsgi.environ
request.wsgi.start_response
request.wsgi.middleware
variable
valor
request.application
examples
request.controller
default
request.function
index
request.extension
html
request.view
status
request.folder
applications/examples/
request.args
request.vars
request.get_vars
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
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:
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
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
response.files : una lista de archivos .css, .js, .coffee, y .less asociados a la pgina. Se
response.include_files() genera etiquetas del encabezado html para incluir todos los
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
para pasar un rbol de mens de navegacin a la vista. Esto puede ser convertido
(render) por el ayudante MENU.
o
response.include_meta() genera
una
cadena
que
incluye
todos
los
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.stream(archivo,
chunk_size,
request=request,
attachment=False,
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
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._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
defecto es:
"%s/%s.%s" % (request.controller, request.function, request.extension)
Cambia el valor de esta variable para modificar el archivo la vista asociado a una accin
particular.
o
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
salida.
o
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
session
session es otra instancia de la clase Storage . Se puede almacenar cualquier cosa en ella,
por ejemplo:
session.myvariable = "hola"
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.
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'))
Aqu cookie_key es
una
clave
de
cifrado
simtrico
(symmetric
encryption
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)
cache
cache es un objeto global que tambin est disponible en el entorno de ejecucin de web2py.
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)
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))
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))
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
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)
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.
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
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')
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
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')
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')
La funcin URL adems acepta un argumento port para especificar el puerto del servidor si
es necesario.
salt : una cadena opcional para utilizar la tcnica salt antes de la firma
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():
@auth.requires_signature()
def dos():
# hacer algo
return locals()
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
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
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')
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',)
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'))
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)
Traduccin de variables
T(...) no slo traduce las cadenas sino que adems puede traducir los valores contenidos en
variables:
>>> a="test"
>>> print T(a)
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
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
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 (!)
Por ejemplo:
o
%%{palabra(numero)} permite
establecer
un numero en
forma
directa
(por
ejemplo: %%{palabra(%i)} )
o
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))
o
T("%%{este[0]} %%{es[0]} %%{el[0]} %s %%{libro[0]}", var)
1 este es el 1 libro
2 estos son los 2 libros
3 estos son los 2 libros
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
...
con
T.M("hola mundo")
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
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.
Omitir los nombres de la aplicacin, controlador y funcin por defecto en las URL
visibles desde afuera (las creadas por la funcin URL())
Eliminar un prefijo determinado de las URL entrantes y aadirlo nuevamente a las URL
salientes
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'),
)
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
se asociar con:
applications/miapp/static/it/archivo
se asociarn como:
applications/miapp/static/base.css
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.
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'),
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'),
)
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+'
se asociar a
'/prueba/default/index?vars=\g<any>'
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.
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
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
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.
En "routes.py" puedes adems especificar una accin que se encargar de manejar los
errores:
error_handler = dict(application='error',
controller='default',
function='index')
(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 .
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
por esto:
location ~* /(\w+)/static(?:/_[\d]+.[\d]+.[\d]+)?/(.*)$ {
alias /home/www-data/web2py/applications/$1/static/$2;
expires max;
}
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
[cron]
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.
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
Segn cmo ests corriendo web2py, hay cuatro modos de operacin para el web2py cron.
o
o
"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 .
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.
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
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
)
db es la instancia de base de datos DAL donde se crearn las tablas del planificador.
worker_name es por defecto None. Tan pronto como se inicie el obrero, se generar
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
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
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}
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
TIMEOUT cuando
hayan
pasado
ms
de n segundos
especificados
con
el
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).
Resumiendo: dispones de
o
timeout para asegurarte que la funcin no exceda una cierta cantidad de tiempo de
ejecucin
o
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,
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}))
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'}>
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
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
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
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
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
)
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
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
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
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
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()
Cooperacin
Hay varias formas de cooperacin entre aplicaciones:
o
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 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.
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)
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
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
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.
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
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 = ('<?','?>')
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)}}
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
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
que produce:
<ul>
<li>3</li>
<li>2</li>
<li>1</li>
</ul>
if...elif...else
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
{{finally}}
<br />
{{pass}}
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
{{return}}
<ul>
{{itemize2('www.google.com')}}
</ul>
Ayudantes HTML
Si tenemos el siguiente cdigo en una vista:
{{=DIV('esta', 'es', 'una', 'prueba', _id='123', _class='miclase')}}
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 <mundo></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>
>>> 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
XML
XML es un objeto que se utiliza para encapsular texto que no debera escaparse. El texto
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)
<script>alert("no es seguro!")</script>
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
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 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
BODY
BR
Ten en cuenta que los ayudantes se pueden repetir utilizando el operador de multiplicacin:
CAT
CENTER
_class='prueba', _id=0)
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>
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"><hola><b>mundo</b></div>
EM
Enfatiza su contenido
>>> print EM('<hola>', XML('<b>mundo</b>'), _class='prueba', _id=0)
<em id="0" class="prueba"><hola><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
HEAD
<head><title><hola><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><hola><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
donde doctype puede ser 'strict', 'transitional', 'frameset', o una cadena doctype completa.
HR
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']:
>>>
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
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
LEGEND
LI
META
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>
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
http://...mp3
<audio src="http://...mp3"></audio>
http://...mp4
<video src="http://...mp4"></video>
qr:http://...
embed:http://...
<iframe src="http://..."></iframe>
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
y tablas con:
---------X|0|0
0|X|0
0|0|1
----------
crea
'cbcb'
_src='http://www.web2py.com')
<object src="http://www.web2py.com"><hola><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><hola></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
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>
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.
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="<hola>"><hola></option>
<option value="<b>mundo</b>"><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"><hola><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>
//--></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>
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><hola></td></tr></tbody>
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"><hola mundo></textarea>
TFOOT
TH
THEAD
TITLE
TR
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><hola></li><li><b>mundo</b></li></ul>
embed64
embed64(filename=None,
file=None,
data=None,
extension='image/gif') codifica
la
Ayudantes personalizados
TAG
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 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):
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
internos.
o
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)}}
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
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>
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:
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'))
o
>>> a = DIV(SPAN('abcde'), DIV('fghij'))
>>> d = a.elements(find=re.compile('fg\w{3}'))
>>> print d[0]
<div>fghij</div>
components
parent
y siblings
>>> 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
flatten
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
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>
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
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>
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}}
que
esto
slo
funcionar
para include --
no
puedes
poner
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 .
{{
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>
<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-->
<!-- 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.
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).
En las vistas, puedes habilitar o personalizar las barras laterales de esta forma:
{{left_sidebar_enable=True}}
{{extend 'layout.html'}}
{{block left_sidebar}}
Este texto va en la barra lateral
{{end}}
[bootstrap]
"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.
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.
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.
{{end}}
</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>
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
PostgreSQL
MySQL
Oracle
cx_Oracle [cxoracle]
MSSQL
pyodbc [pyodbc]
FireBird
DB2
pyodbc [pyodbc]
Informix
informixdb [informixdb]
Ingres
ingresdbi [ingresdbi]
Cubrid
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
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'))
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()
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
En
la
mayora
de
los
casos
puedes
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.
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']
postgres(_nonreserved)
MySQL
mysql
FireBird
firebird(_nonreserved)
MSSQL
mssql
Oracle
oracle
DAL
, Table, Field
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)
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')
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".
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
s es usado por SQLFORM. Los validadores por defecto para cada tipo de campo se
listan en la siguiente tabla:
tipo de campo
string
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>
list:string
None
list:integer
None
list:reference <table>
json
IS_JSON()
bigint
None
big-id
None
big-reference
None
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.
notnull=True se traduce en una instruccin "NOT NULL" de SQL. Esto evita que la
valores del campo sean nicos para la tabla. Esto se controla en el nivel de la base de
datos.
o
ruta, los archivos subidos se almacenarn en una carpeta distinta. Por ejemplo,
Field(..., uploadfolder=os.path.join(request.folder, 'static/temp'))
widget debe
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
update contiene el valor por defecto para este campo cuando se actualice el registro.
authorize se puede usar para requerir control de acceso para ese campo, solo para
represent puede ser None o una funcin; la funcin recibe el valor actual del campo
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']
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'>
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')
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.
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
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')
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()
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
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
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.
Otros dialectos de bases de datos usan una sintaxis muy similar pero pueden no soportar la
instruccin "IF NOT EXISTS".
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)
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://...')
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.
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',
Query
, Set, Rows
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
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
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]
Atajos
La DAL soporta varios atajos para simplificar el cdigo fuente.
En especial:
miregistro = db.mitabla[id]
del db.mitabla[id]
y esto es equivalente a
db(db.mitabla.id==id).delete()
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.
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'))
que es equivalente a
>>> cosas = db(db.cosa._id>0).select()
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.
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)
headers un diccionario que asocia nombres de campo a las etiquetas que se usarn
defecto es 16)
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
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
Alejandro
Roberto
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
Operadores lgicos
Las consultas se pueden combinar con el operador binario " & ":
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
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'
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
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)
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):
...
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')
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
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
son equivalentes a
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
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
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')
La funcin
resultado = db.mitabla.validate_and_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')
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
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
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.
Field('precio_unitario', 'double'),
Field('cantidad', 'integer'))
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))
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'),
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).
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'))
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
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()
def precio_total_perezoso(self):
return lambda self=self: self.item.precio_unitario \
* self.item.cantidad
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
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:
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
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)])
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
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'))
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
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
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'))
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)
Observa que los campos list:reference etiqueta recibe una restriccin por defecto
requires = IS_IN_DB(db, 'etiqueta.id', db.etiqueta._format, multiple=True)
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
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)
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)
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
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 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:
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])
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
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.
Esto es equivalente a
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.
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, ...
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
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 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.
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
quotechar : el carcter a usar para encerrar (quote) valores de cadena (por defecto
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:
[quoteall]
de
la
base
de
datos,
pero
no
del
objeto Rows en
s.
Cuando
el
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
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
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))
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.
>>> 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.
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',
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()
los
cuales
el
el
valor
False.
El
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)
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
o ignorarlo:
db(consulta, ignore_common_filters=True).select(...)
comprimido = SQLCustomType(
type ='text',
native='text',
encoder =(lambda x: zlib.compress(x or '')),
decoder = (lambda x: zlib.decompress(x))
)
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.
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:
Esto nos permite acceder a toda tabla db.tabla `sin tener que redefinirla.
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))"
(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
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
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'
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
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')
valor) y
llama
al
mtodo
de
...
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
}
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={})
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
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,
FORM
Tomemos como ejemplo una aplicacin de prueba con el siguiente controlador "default.py":
def mostrar_formulario():
return dict()
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)
Observa que:
o
Todo el trabajo lo hace el mtodo accepts del objeto formulario . El mtodo filtra los datos
de request.vars segn
los
requerimientos
aquellas
declarados
variables
que
(por
medio
pasan
la
de
los
validacin
un
error
el
error
se
almacenar
en formulario.errors .
a request.vars . El primero contiene los valores que pasaron la validacin, por ejemplo:
formulario.vars.nombre = "Maximiliano"
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):
primer
argumento
simplemente request .
puede
Este
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:
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
message_onfailure
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 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 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)
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()
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)
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
<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>
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'))
fields es una lista opcional de nombres de campos que quieres mostrar. Si la lista se
labels es un diccionario de etiquetas para los campos. Los nombres o key son
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:'}
usuario que permiten que el formulario maneje campos de tipo reference. Esto se tratar
con ms detalle en otra seccin.
o
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
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
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()
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
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'))
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():
def download():
return response.download(request, db)
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:
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='...')
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
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.
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"}
Precompletando el formulario
Siempre es posible precompletar el formulario usando la sintaxis:
formulario.vars.nombre = 'valorcampo'
SQLFORM
INPUT(_name='deacuerdo',value=True,_type='checkbox'))
formulario[0].insert(-1, mi_elemento_adicional)
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'
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
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)
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')
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()
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)
nombredetabla.
o
registro id en nombredelatabla.
o
registro id en nombredelatabla.
o
de la tabla.
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())
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
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()
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 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
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:'
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.
contiene la cadena "[id]" esta ser reemplazada por el id del registro actual procesado
por el formulario.
o
formulario "update".
o
queries es una lista como ['equals', 'not equal', 'contains'] que contiene una serie de
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
showall configralo como True si quieres que muestren los registros de la consulta la
chkall configralo como True si quieres que todas las opciones checkbox del
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)))
Observa
que
la
lnea tabla.id.represent=... le
indica
web2py
como
cambiar
la
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
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'))
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}}
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
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.
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
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}}
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).
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.
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 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
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 .
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
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
Ejemplos:
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 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')
requires = IS_MATCH('^1?((-)\d{3}-?|\(\d{3}\))\d{3}-?\d{4}$',
error_message='No es un nmero de telfono')
# 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
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
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')
Donde
o
o
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
Puedes usar (-1, -1) como minsize para omitir la comprobacin del tamao de la imagen.
Aqu se muestran algunos ejemplos:
o
requires = IS_IMAGE()
requires = IS_EMPTY_OR(IS_IMAGE())
IS_UPLOAD_FILENAME
filename: expresin regular para comprobar el nombre del archivo (sin la extensin).
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.
IS_IPV4
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
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()
IS_UPPER
IS_NULL_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')
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.
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
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
comando select.
IS_IN_DB
y selecciones 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))
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)
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)
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
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)
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)
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
y SQLFORM.smartgrid
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
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('↑'), XML('↓')),
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
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=...) .
groupby se
usa
para
agrupar
la
consulta.
Utiliza
la
misma
sintaxis
que select(groupby=...) .
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)))
csv si se establece como true permite que se descarguen los registros en mltiples
links se usa para mostrar columnas adicionales que pueden ser link a otras pginas.
links_in_grid si se establece como False, los link solo se mostrarn en las pginas
upload funciona de la misma forma que con SQLFORM. web2py usa la accin de ese
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
sorter_icons es una lista de cadenas (o ayudantes) que se usarn para presentar las
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')
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)'))
formargs se
pasa
todo
objeto
SQLFORM
que
use
el
grid,
'left', 'both') que especifica la posicin en la visualizacin de los registros para los botones
(o los link).
deletable
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.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()
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()
El smargrid toma los mismos argumentos como grid y algunos ms, con algunos detalles a
tener en cuenta:
o
divider permite
especificar
un
carcter
que
se
usar
en
el
navegador
Todos
los
argumentos
excepto
el
de
la
tabla, args , linked_tables y user_signatures aceptan un diccionario segn se detalla
ms abajo.
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)
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"
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
auth_group
auth_membership
muchos.
o
auth_permission
auth_event
auth_cas
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
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()
secure=True
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
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())
{{pass}}
{{pass}}
</div>
form
if
auth()
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.
groups lista los grupos en los que est incluido el usuario como
miembro.
navbar es un ayudante
login/registrarse/etc.
que
genera
una
barra
con
links
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
El
decorador
decoradores
auth.requires_login()
auth.requires_*
as
como
tambin
los
dems
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.
registration_key
url
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"])
db.auth_user
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.
[
Recaptcha
Observa el
options
use_ssl=False
por defecto.
options="theme:'white',
lang:'es'"
Recaptcha
en
Recaptcha
DIV
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'))
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
# requerido
# requerido
## 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)
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()
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
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==
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
basic_auth
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
auth_user
auth
El primer argumento de
email_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'))
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
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:
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
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
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
mltiples
pasos
del
lado
de otro_formulario.formulario_acceso .
del
acceso
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
archive_names
cada tabla.
current_record
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
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
enable_record_versioning
no deberas usar
auth.archive
crud.archive
o de lo
que
enable_record_versioning
que
enable_record_versioning
lo hace automticamente.
Auth
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
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'
auth.messages
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
False
create_user_groups
no es un valor booleano
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.
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)
id_usuario
auth.add_membership(id_grupo, id_usuario)
id_grupo
al usuario
id_usuario
. Si el usuario no se
expulsa al usuario
id_usuario
del grupo
id_grupo
id_usuario
id_usuario
id_grupo
id_grupo
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
id_grupo
autenticado.
Tambin puedes usar
auth.id_group(role="...")
anula el permiso.
auth.has_permission('nombre', 'objeto', id_registro, id_usuario)
id_usuario
permiso consultado.
registro = db(auth.accessible_query('read', db.mitabla, id_usuario))\
.select(db.mitabla.ALL)
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(...)
He aqu un ejemplo:
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'
import os
for file in os.listdir('./'):
os.unlink(file)
return 'se borraron todos los archivos'
@auth.requires_permission('sumar', 'nmero')
def sumar(a, b):
return a + b
def funcion_siete():
return sumar(3, 4)
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
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.
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)
Autorizacin y descargas
El uso de decoradores y de
crud.settings.auth
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'))
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".
@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'
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:
auth_user
username
no tiene un campo
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
Por ejemplo:
auth.settings.actions_disabled.append('register')
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
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
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')
url
pasado a esta
on_failed_authorization
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))
auth
auth.settings.retrieve_username_captcha = None
auth.settings.retrieve_password_captcha = None
Si los parmetros de
.captcha
hacen referencia a
gluon.tools.Recaptcha
None
.captcha
como
.login_captcha
) se haya
, todos los
None
False
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'
auth.settings.label_separator = ':'
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)"
add|del|has
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
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='...')
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
que devuelve
true
si el usuario se autentic y
false
true
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()
complejo. Esta accin puede recuperar el URL que fue originalmente solicitado usando la
variable de entorno
request.env.request_uri