Generalidades

Inicio

Web2py viene en paquetes binarios para Windows y Mac OS X. También hay una versión de código fuente que se ejecuta en Windows, Mac, Linux y otros sistemas Unix. Las versiones Windows y OS X binarias incluyen el necesario intérprete de Python. El paquete del código fuente de Python asume que este ya está instalado en el equipo.

Web2py no requiere instalación. Para empezar, descomprimir el archivo zip descargado para su sistema operativo y ejecutar el archivo web2py correspondiente.

En Windows, ejecute:

1
web2py.exe

En OS X, ejecute:

open web2py.app

En Unix y Linux, ejecute desde la fuente tipeando:

python2.5 web2py.py

El programa web2py acepta varias opciones de línea de comandos que se describen más adelante.

Por defecto, en el inicio, web2py muestra una ventana de inicio:

MVC

y luego muestra una aplicación GUI que le pide elejir una contraseña de administrador una sola vez, la dirección IP de la interfaz de red que se utilizará para el servidor web y un número de puerto desde donde servir peticiones. Por defecto, web2py ejecuta su servidor web en 127.0.0.1:8000 (el puerto 8000 en localhost), pero se puede ejecutar en cualquier dirección IP y puerto disponible. Se puede consultar la dirección IP de su interfaz de red mediante la apertura de una línea de comandos y escribir ipconfig en Windows o ifconfig en OS X y Linux. A partir de ahora suponemos que web2py se está ejecutando en localhost (127.0.0.1:8000). Se usa 0.0.0.0:80 para ejecutar web2py públicamente en cualquiera de las interfaces de red.

MVC

Si usted no proporciona una contraseña de administrador, la interfaz de administración está deshabilitada. Esta es una medida de seguridad para evitar exponer públicamente la interfaz admin.

La interfaz administrativa, admin, sólo es accesible desde localhost a menos que ejecute web2py soportando Apache con mod_proxy. Si admin detecta un proxy, la cookie de sesión se establece para bloquear y la entrada al sistema de admin no funciona a menos que la comunicación entre el cliente y el proxy vaya a través de HTTPS, esto es una medida de seguridad. Todas las comunicaciones entre el cliente y admin siempre debe ser local o encriptadas, de lo contrario un atacante podría ser capaz de realizar un ataque de hombre-en-el-medio o un ataque de réplica y ejecutar código arbitrario en el servidor.

Después de la contraseña de administración se ha fijado, web2py pone en marcha el navegador de Internet en la página:

http://127.0.0.1:8000/

Si el equipo no tiene un navegador por defecto, abra un navegador web e introduzca la dirección URL.

MVC

Haciendo click en “administrative interface” lleva a la página de entrada al sistema para la interface de administración.

MVC

La contraseña del administrador es la contraseña elegida en el inicio. Se toma en cuenta que sólo hay un administrador, por lo que sólo hay una contraseña de administrador. Por razones de seguridad, al desarrollador se le pide que elija una contraseña nueva cada vez que se inicia web2py a menos que la opción <recycle> se especifique. Esto es distinto del mecanismo de autenticación de aplicaciones en web2py.

Después de que el administrador inicia sesión en web2py, el navegador es redirigido al “sitio” de la página.

MVC

Esta página muestra todas las aplicaciones instaladas en web2py y permite al administrador gestionarlas. web2py viene con tres aplicaciones:

  • Una aplicación de admin, la que seestá usando en este momento.
  • Una aplicación de ejemplos (examples), con la documentación en línea interactiva y una réplica de la página web oficial de web2py.
  • Una aplicación de bienvenida (welcome). Este es el modelo básico para cualquier aplicación web2py otros. Esta es llamada la aplicación de andamios. Esta es también la aplicación que da la bienvenida a un usuario en el inicio.

Las aplicaciones listas para usar son llamadas dispositivos web2py. Se pueden descargar muchos dispositivos disponible libremente desde 34. Los usuarios de web2py son incentivados a inscribir nuevos dispositivos, ya sea en código abierto o en código cerrado (ya compilado y empaquetado).

Desde la página de la aplicación admin, se pueden realizar las siguientes operaciones:

  • Instalar (install) una aplicación completando el formulario en la parte inferior derecha de la página. Asigne un nombre a la aplicación, seleccione el archivo que contiene una aplicación empaquetada o la URL donde se encuentra la aplicación, y haga clic en “enviar” (submit).

  • Desinstalar (uninstall) una aplicación haciendo clic en el botón correspondiente. Hay una página de confirmación.

  • Crear (create) una nueva aplicación eligiendo un nombre y haciendo clic en “enviar” (submit).

  • Empaquetar (package) una aplicación para su distribución haciendo clic en el botón correspondiente. Una aplicación de descarga es un archivo tar que contiene todo, incluyendo la base de datos. Usted no debe descomprimir este archivo; web2py lo hace automáticamente cuando se instalan con admin.

  • Limpieza de archivos temporales (clean up) de una aplicación tales como sesiones, errores y los archivos caché.

  • Editar (EDIT) una aplicación.

    Al crear una nueva solicitud usando admin, esta comienza como un clon de la aplicación de andamio “welcome” con un “model/db.py” que crea una base de datos SQLite, se conecta a ella, crea instancias de Auth, Crud y Servicio, y los configura. También proporciona un “controller/ default.py”, que expone las acciones “índice” (index), “descargar” (download), “usuario” (user) para la gestión de usuarios, y “llamada” (call) para los servicios. A continuación, se supone que estos archivos se han eliminado, vamos a estar creando aplicaciones desde cero.

Diga “Hello”

Aquí, como ejemplo, se crea una sencilla aplicación web que muestra el mensaje “Hello from MyApp” al usuario. Vamos a llamar a esta aplicación “myapl”. También vamos a agregar un contador que cuenta las veces que el usuario visita la página. Se puede crear una nueva aplicación con sólo escribir su nombre en la forma de la parte superior derecha de la página web de admin.

MVC

Después de presionar enviar [submit], la aplicación es creada como una copia de la applicación incorporada welcome.

MVC

Para ejecutar la aplicación nueva se debe visitar:

http://127.0.0.1:8000/myapp

Ahora se tiene una copia de la aplicación welcome.

Para editar una aplicación, hacer clic en el botón edit para al aplicación creada recientemente.

MVC

La página EDIT dice qué está dentro de la aplicación. Cada aplicación web2py consta de ciertos archivos, la mayoría de los cuales pertenecen a una de estas cinco categorías:

  • Modelos (models): describen la representación de datos.
  • Controladores (controllers): describen la lógica de la aplicación y flujo de trabajo.
  • Vistas (views): describen la presentación de datos.
  • Idiomas (languages): describen la manera de trasladar la presentación de solicitud a otros idiomas.
  • Módulos (modules): módulos de Python que pertenecen a la aplicación.
  • Archivos Estáticos (static files): imágenes estáticas, archivos CSS, archivos JavaScript, etc

Todo está cuidadosamente organizado basado en el patrón de diseño Modelo-Vista-Controlador. Cada sección en la página edit corresponde a una subcarpeta en la carpeta de la aplicación.

Hay que tomar en cuenta que los títulos de las secciones se conmutan según el contenido. Los nombres de carpeta en archivos estáticos también son plegables.

Cada archivo enumerado en la sección corresponde a un archivo ubicado físicamente en la subcarpeta. Cualquier operación que se realiza en un archivo a través de la interfaz de admin (crear, editar, eliminar) se puede realizar directamente desde el intérprete de comandos usando cualquier editor.

La aplicación contiene otros tipos de archivos (base de datos, archivos de sesión, los archivos de error, etc), pero que no figuran en la página edit, porque no son creados o modificados por el administrador, sino que son creados y modificados por la propia aplicación.

Los controladores contienen la lógica y el flujo de trabajo de la aplicación. Cada dirección URL se designa en una llamada a una de las funciones de los controladores (acciones). Hay dos controladores por defecto: “appadmin.py” y “default.py”. appadmin proporciona un interfaz con la administración de la base de datos, no lo necesitamos en este momento. “default.py” es el controlador necesario para editar, el que se llama por defecto cuando no se especifica ningún controlador en la URL. Editar el “index” funciona de la siguiente manera:

1
2
def index():
    return "Hello from MyApp"

Aqui se ve como el editor en línea luce:

MVC

Salvémoslo y regresemos a la página edit. Clic en el enlacé del índice para visitar la página que ha sido creada. Cuando se visita la URL

http://127.0.0.1:8000/myapp/default/index

la acción del index en el controlador por defecto de myappl es llamada. Este retorna una cadena que el brownser despliega para que se vea. Debe lucir así:

MVC

Ahora, se edita la función”índx” tal como sigue:

1
2
 def index():
     return dict(message="Hello from MyApp")

También, desde la página edit, se edita la vista default/index (el nuevo archivo asociado con la acción), y en este archivo, se escribe:

1
2
3
4
5
6
<html>
   <head></head>
      <body>
            <h1>{{=message}}</h1>
               </body>
               </html>

Ahora la función retorna un diccionario definiendo un mensaje (message). Cuando una acción retorna un diccionario, web2py busca una vista con el nombre

[controller]/[function].[extension]

y lo ejecuta. Aquí [extension] es la prórroga solicitada. Si no se especifica la extensión, el valor predeterminado es “html”, y eso es lo que vamos a asumir aquí. Bajo este supuesto, la vista es un archivo HTML que se incluye en el código Python usando etiquetas especiales {{}}. En particular, en el ejemplo, el {{=message}} instruye web2py para reemplazar el código marcado con el valor de message devuelto por la acción. Nótese que aquí message no es una palabra clave de web2py sino que se define en la acción. Hasta ahora no hemos utilizado ninguna palabra clave web2py.

Si web2py no encuentra la vista solicitada, utiliza la vista “generic.html” que viene con cada aplicación.

Si otra extensión distinta que “html” se especifica (“json”, por ejemplo), y el archivo vista de “[controlador] / [función]. json” no se encuentra, web2py busca la vista “generic.json”. web2py viene con generic.html, generic.json, generic.xml y generic.rss. Estas vistas genéricas puede ser modificadas para cada aplicación individual, y vistas adicionales se pueden agregar fácilmente.

Lea más sobre este tema en el capítulo 9.

Si se vuelve a “EDIT” y se hace clic en el index, ahora veremos la siguiente página HTML:

MVC

Contemos

Ahora vamos a agregar un contador a esta página que contará cuántas veces el mismo visitante vé la página.

Web2py en forma automática y transparente hace seguimiento de los visitantes usando sesiones y cookies. Por cada nuevo visitante, se crea una sesión y le asigna una única “session_id”. La sesión es un contenedor para las variables que se almacenan en el servidor. El identificador único se envía al navegador a través de una cookie. Cuando el visitante solicita otra página desde la misma aplicación, el navegador envía la cookie, se recuperará por web2py, y el correspondiente período de sesiones se restaura.

Para utilizar el período de sesiones, se modifica el controlador por defecto:

1
2
3
4
5
6
def index():
    if not session.counter:
        session.counter = 1
    else:
        session.counter += 1
    return dict(message="Hello from MyApp", counter=session.counter)

Observe que counter no es una palabra clave de web2py, pero session lo es. Le estamos pidiendo a web2py comprobar si existe una variable de contador en la sesión y, si no, que cree una y le asigne 1. Si el contador está ahí, le pedimos a web2py aumentar el contador en 1. Finalmente pasamos el valor del contador a la vista.

Una forma más compacta de codificar la misma función es la siguiente:

1
2
3
def index():
    session.counter = (session.counter or 0) + 1
    return dict(message="Hello from MyApp", counter=session.counter)

Ahora modifque la vista para añadir una línea que muestre el valor del contador:

1
2
3
4
5
6
7
<html>
   <head></head>
   <body>
      <h1>{{=message}}</h1>
      <h2>Number of visits: {{=counter}}</h2>
   </body>
</html>

Cuando se visita la página index otra vez (y otra) se debe tener la siguiente página HTML:

MVC

El contador se asocia con cada visitante, y se incrementa cada vez que el visitante recarga la página. Diferentes visitantes ven diferentes contadores.

Di mi nombre

Ahora se crean dos páginas (first y second) donde la primera crea una forma, pregunta el nombre de quién la visita, y redirecciona a la segunda página, la cual saluda el visitante por su nombre.

MVC

Se escriben las acciones correspondientes en el controlador por defecto:

1
2
3
4
def first():
    return dict()
def second():
    return dict()

Entonces se crea una vista “default/first.html” para la primera acción:

MVC

y se inserta:

1
2
3
4
5
6
{{extend 'layout.html'}}
What is your name?
<form action="second">
  <input name="visitor_name" />
  <input type="submit" />
</form>

Finalmente se crea una vista “default/second.html” para la segunda acción:

1
2
{{extend 'layout.html'}}
<h1>Hello {{=request.vars.visitor_name}}</h1>

En el diseño de ambas vistas, hemos ampliado la la vista básica “layout.html” que viene con web2py. La vista de diseño mantiene el aspecto de las dos páginas coherente. El archivo de diseño se puede editar y volver a montarse fácilmente, ya que principalmente contiene código HTML.

Si ahora se visita la primera página, al escribir su nombre:

MVC

y enviar la forma, se recibirá el saludo:

MVC

Postbacks (restitución de datos)

El mecanismo para el envío de formas que utilizamos antes es muy común, pero no es una buena práctica de programación. Todas las entradas deben ser validadas y, en el ejemplo anterior, la carga de validación caería en la segunda acción. Así, la acción que realiza la validación es diferente de la acción que generó el formulario. Esto tiende a provocar redundancia en el código.

Un mejor modelo para el envío de la forma es enviar los formularios a la misma acción que los genera, en nuestro ejemplo, la “first”. La acción “first” debe recibir las variables, procesarlas, almacenarlas en el servidor, y redirigir al visitante a la “segunda” página, que recupera las variables. Este mecanismo se llama restitución de datos - postback.

MVC

Se modifica el controlador por defecto para implementar la auto-presentación:

1
2
3
4
5
6
7
8
def first():
    if request.vars.visitor_name:
        session.visitor_name = request.vars.visitor_name
        redirect(URL('second'))
    return dict()

def second():
    return dict()

Luego se modifica la vista “default/first.html”:

1
2
3
4
5
6
{{extend 'layout.html'}}
What is your name?
<form>
  <input name="visitor_name" />
  <input type="submit" />
</form>

Y la vista “default/second.html”necesita recuperar los datos de la session en lugar que desde request.vars:

1
2
{{extend 'layout.html'}}
<h1>Hello {{=session.visitor_name or "anonymous"}}</h1>

Desde el punto de vista del visitante, la auto-presentación se comporta exactamente igual que en la implementación anterior. No hemos añadido la validación todavía, pero ahora está claro que la validación debe ser realizada por la primera acción.

Este enfoque también es mejor porque el nombre del visitante permanece la sesión, y se puede acceder por todas las acciones y vistas en las aplicaciones sin tener que ser pasado a ellas de forma explícita.

Tenga en cuenta que si la “second” acción se llama alguna vez antes de que un nombre de visitante se coloque, se verá en pantalla “Hello anonymous” porque session.visitor_name retorna None. También podríamos agregar el siguiente código en el controlador (dentro de la función second):

1
2
if not request.function=='first' and not session.visitor_name:
    redirect(URL('first'))

Se trata de un mecanismo general que puede utilizar para hacer cumplir la autorización en los controladores, aunque debe verse el Capítulo 8 para un método más poderoso.

Con web2py podemos avanzar un paco más y pedirle que genere la forma por nosotros, incluyendo la validación. web2py provee ayudas (FORM, INPUT, TEXTAREA y SELECT/OPTION) con los mismos nombres equivalentes a etiquetas HTML. Pueden ser utilizados para construir las formas, ya sea en el controlador o en la vista.

Por ejemplo, aquí hay una forma posible de reescribir la primera acción:

1
2
3
4
5
6
7
def first():
    form = FORM(INPUT(_name='visitor_name', requires=IS_NOT_EMPTY()),
                INPUT(_type='submit'))
    if form.accepts(request.vars, session):
        session.visitor_name = form.vars.visitor_name
        redirect(URL('second'))
    return dict(form=form)

donde se dice que la FORM etiqueta contiene dos etiquetas INPUT. Los atributos de las etiquetas de entrada son especificados por los argumentos nombrados a partir de subrayado. El argumento requires no es un atributo de etiqueta (porque no comienza con subrayado) pero establece un validador para el valor de visitor_name.

El objeto form puede ser fácilmente serializado en HTML insertándolo en la vista “default/first.html”.

1
2
3
{{extend 'layout.html'}}
What is your name?
{{=form}}

El método form.accepts aplica los validadores. Si el formulario de auto-presentación es validado, este almacena las variables de la sesión y las restituye como antes. Si el formulario no pasa la validación, los mensajes de error se insertan en el formulario y se muestran al usuario, como a continuación:

MVC

En la siguiente sección vamos a mostrar cómo las formas se pueden generar automáticamente a partir de un modelo.

Un Blog Image

Aquí, como otro ejemplo, queremos crear una aplicación web que permita al administrador colocar imágenes y darles un nombre, y permite que los visitantes del sitio web ver las imágenes con nombre y enviar comentarios.

Al igual que antes, se crea la nueva aplicación desde la página web de admin y se navega a la página de edit:

MVC

Comenzamos creando un modelo, una representación de los datos persistentes en la aplicación (las imágenes a subir, sus nombres, y los comentarios). Primero, se necesita crear/editar un archivo modelo que, por falta de imaginación, llamamos “db.py”. Asumimos que el código que se vé abajo, reemplazará cualquier código existente en “db.py”. Modelos y controladores deben tener una extensión .py ya que son código Python. Si la extensión no se provee, web2py la incluye. Vistas, en cambio tienen una extensión .html ya que principalmente contienen código HTML.

Se edita el archivo”db.py” haciendo clic en el correspondiente botón “editar”:

MVC

Y se inserta lo siguiente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
db = DAL("sqlite://storage.sqlite")
db.define_table('image',
   Field('title'),
   Field('file', 'upload'))
db.define_table('comment',
   Field('image_id', db.image),
   Field('author'),
   Field('email'),
   Field('body', 'text'))
db.image.title.requires = IS_NOT_IN_DB(db, db.image.title)
db.comment.image_id.requires = IS_IN_DB(db, db.image.id, '%(title)s')
db.comment.author.requires = IS_NOT_EMPTY()
db.comment.email.requires = IS_EMAIL()
db.comment.body.requires = IS_NOT_EMPTY()
db.comment.image_id.writable = db.comment.image_id.readable = False

Analizemos esto línea a línea:

Línea1 defines una variable global llamada db que representa la conneción a la base de datos. En este caso es una conección a una base de datos SQLite guardada en el archivo “applications/images/databases/storage.sqlite”. En el caso de SQLite, si la base de datos no existe, se crea. Se puede cambiar el nombre del archivo, asi como el de la variable global db, pero es conveniente darles el mismo nombre, para hacerlo fácíl de recordar.

Líneas 3-5 definen la tabla “image”. define_table es un método del objeto db. El primer argumento, “image”es el nombre de la tabla que estamos definiendo. Los otros argumentos son los campos pertenecientes a la tabla. Esta tabla tiene un campo llamado “title”, un campo llamado “file”, y un campo llamado “id” que sirve como clave primaria de la tabla (“id” no se declara explícitamente porque todas las tablas tienen un campo id por defecto). El campo “field” es una cadena, y el campo “file” es del tipo “upload”. “upload” es un tipo especial de campo usado por la capa de abstración de datos (DAL) para guardar los nombres de archivos que se han bajado. web2py sabe como bajar archivos (via streaming en caso de que sean muy largos), renombrarlos en forma segura y guardarlos.

Cuando una tabla se define, web2py toma una de varias acciones posibles: a) si la tabla no existe, se crea la tabla; b) si la tabla existe y no se corresponde con la definición, se altera como corresponde, y si un campo tiene un tipo diferente, web2py intenta convertir su contenido, c) si la tabla existe y corresponde a la definición, web2py no hace nada.

Este comportamiento se denomina “migración”. En web2py las migraciones son automáticas, pero se puede desactivar para cada tabla pasando migrate=False como el último argumento de define_table.

Líneas 7-11 define otra tabla llamada “coment”. Un comentario tiene un “author”, un “email” (tenemos la intención de almacenar la dirección de correo electrónico del autor del comentario), un “body” de tipo “text” (tenemos la intención de usarlo para almacenar en el comentario publicado por el autor), y un campo”image_id” de tipo de referencia que apunta a db.image a través del campo”id”.

En las línea 13 db.image.title representa el campo “title” de la tabla “image”. El atributo requires le permite establecer los requisitos/limitaciones que serán reforzadas por las formas web2py. Aquí es necesario que el “title” sea único.

1
IS_NOT_IN_DB(db, db.image.title)

Los objetos que representan a esas limitaciones se llaman validadores. Validadores múltiples pueden agruparse en una lista. Los validadores se ejecutan en el orden en que aparecen. IS_NOT_IN_DB(a, b) es un validador especial que comprueba que el valor de b para un nuevo registro, no aparece ya en a.

La línea 14 requiere que el campo “image_id” de la tabla “coment” esté en db.image.id. En cuanto a la base de datos se refiere, que ya había declarado esto cuando se definió la tabla “coment”. Ahora le estamos diciendo de forma explícita al modelo que esta condición debe ser reforzada por web2py, también, a nivel de procesamiento de formas cuando un nuevo comentario sea publicado, de modo que los valores no válidos no se propagan a partir de formas de entrada a la base de datos. También se requiere que el “image_id” esté representado por el “title”, ‘%(title)s’, del registro correspondiente.

La línea 18 indica que el campo “image_id” del cuadro “coment” no debe ser mostrado en las formas, writable=False y ni siquiera en las formas de sólo lectura, readable=False.

El significado de los validadores en las líneas 15-17 debería ser obvio.

Note que el validado

1
db.comment.image_id.requires = IS_IN_DB(db, db.image.id, '%(title)s')

Puede omitirse (lo cual sería automático) si fueramos a especificar un formato para representar una imagen:

db.define_table('image',....,format='%(title)s')

donde el formato puede ser una cadeba o una función que toma un registro y retorna una cadena.

Una vez que se define el model, si no hay errores, web2py cre una interface de administración de la aplicación para manejar la base de datos. Se accede a través de el enlace “database administration” en la página edit o de forma directa:

http://127.0.0.1:8000/images/appadmin

Aqui una vista de la pantalla de la interface appadmin:

MVC

Esta interfaz se codifica en el controlador llamado “appadmin.py” y la vista correspondiente “appadmin.html”. De ahora en adelante, nos referiremos a esta interfaz simplemente como appadmin. Esta permite al administrador de base de datos insertar nuevos registros, editar y borrar los registros existentes, consultar tablas, y enlazar bases de datos.

La primera vez que appadmin se accesa, el modelo se ejecuta y se crean las tablas. El DAL de web2py traduce el código Python en instrucciones SQL que son específicas del motor interno de la base de datos seleccionada, (SQLite en este ejemplo). Se puede ver el SQL generado a partir de la página de edición haciendo clic en el “sql.log” bajo la sección “models”. Observese que el vínculo no está presente hasta que las tablas se han creado.

MVC

Si se fuera a editar el modelo y accesar appadmin otra vez, web2py podría general SQL para alterar las tablas existentes. El SQL generado se registra en “sql.log”.

Ahora, de regreso a appadmin y tratando de insertar un nuevo registro de imagen:

MVC

web2py ha traducido el campo “upload” de db.image.file en una forma de carga para el archivo. Cuando se envía la forma y un archivo de imagen se carga, el archivo se renombra de un modo seguro que preserve la extensión, se guarda con el nuevo nombre en la carpeta de la aplicación “uploads”, y el nuevo nombre se almacena en campo db.image.file. Este proceso está diseñado para prevenir ataques de directorio transversal.

Nótese que cada tipo de campo se representa por un widget. Widgets por defecto se puede reemplazar.

Al hacer clic en un nombre de tabla en appadmin, web2py realiza una selección de todos los registros en la tabla actual, identificados por un query del DAL

1
db.image.id > 0

Y dá el resultado.

MVC

Se puede seleccionar un conjunto diferente de registros editando el query SQL y presionando “apply”.

Para editar o borrar un simple registro, se le dá clic al numero id del registro.

MVC

A causa del validador IS_IN_DB el campo de referencia “image_id” es presentado en un menú drop-down. Los items en el menú drop-down son guardados como claves (db.image.id), pero son representados por su db.image.title, tal como se ha especificado por el validador.

Validadores son poderosos objetos que saben como representar campos, filtrar valores de campos, generar errores y formatear valores extraídos de el campo.

La siguiente figura muestra que ocurre cuando se envía una froma que no pasa la validación:

MVC

Las mismas formas que se generan automáticamente por appadmin también se puede generar mediante programación a través del SQLFORM helper que está integrado en las aplicaciones de usuario. Estas formas son amigables con CSS de usar, y puede ser personalizadas.

Cada aplicación tiene su propia appadmin, por lo que la appadmin misma puede ser modificada sin afectar a otras aplicaciones.

Hasta ahora, la aplicación sabe cómo almacenar los datos, y hemos visto cómo acceder a la base de datos a través de appadmin. El acceso a appadmin está restringido al administrador, y no pretende ser una interfaz web para la aplicación de producción, de ahí la siguiente parte de este camino. En concreto queremos crear:

  • Una página “index” que muestre todas las imágenes disponibles ordenadas por títulos y enlaces a páginas de detalles de las imágenes.
  • Una página “show/[id]” página que le muestra al visitante la imagen solicitada y permite al visitante ver y enviar comentarios.
  • Una acción “download/[name]” para descargar las imágenes subidas.

Esto se representa esquemáticamente aqui:

MVC

Se regresa a la página edit y se edita el controlador “default.py”, reemplazando su contenido por lo siguiente:

1
2
3
def index():
    images = db().select(db.image.ALL, orderby=db.image.title)
    return dict(images=images)

Esta acción devuelve un diccionario. Las claves de los elementos en el diccionario se interpretan como variables pasadas a la vista asociada a la acción. Si no hay vista, la acción se representa por la vista “generic.html” que se entrega con todas las aplicaciones web2py.

La acción index realiza una selección de todos los campos (db.image.ALL) de todos los campos de la tabla imagen ordenados por db.image.title. El resultado de la selección es un objeto Rows que contiene los registros. Asignar esto a una variable local llamada images devuelta por la acción a la vista. Images es iterativa y sus elementos son las filas seleccionadas. Para cada fila, las columnas se puede acceder como diccionarios: images[0][‘title’] o, equivalentemente, como images[0].title.

Si no se diseña una vista, el diccionario es traducido por “views/generic.html”y una llamada a la acción index se vería así:

MVC

Todavía no ha creado una vista para esta acción, sin embargo, web2py ofrece el conjunto de registros en forma de tabla sin formato.

Se procede a crear una vista para la acción index. Se vuelve a admin, se edita “default/index.html” y se reemplaza su contenido con lo siguiente:

1
2
3
4
5
6
7
{{extend 'layout.html'}}
<h1>Current Images</h1>
<ul>
{{for image in images:}}
{{=LI(A(image.title, _href=URL("show", args=image.id)))}}
{{pass}}
</ul>

La primera observación es que esta perspectiva es HTML puro, con etiquetas especiales {{...}}. El código insertado en {{...}} es puro código Python con una advertencia: el sangrado es irrelevante. Los bloques de código comienzan con las líneas que terminan en dos puntos (:) y terminan en las líneas que empiezan con la palabra clave pass. En algunos casos, el final de un bloque es evidente por el contexto y el uso de pass no es obligatorio.

Líneas 5-7 ciclo sobre las filas de imágenes y para cada fila de visualización de la imagen:

LI(A(image.title, _href=URL('show', args=image.id))

Esta es una etiqueta <li>...</li> que contiene una etiqueta <a href=”...”>...</a> la cual contiene contiene la image.title. El valor de la referencia de hipertexto (atributo href) es:

1
URL('show', args=image.id)

es decir, la dirección URL dentro de la misma aplicación y controlador según la solicitud actual que llama a la función llamada “show”, pasando un solo argumento a la función, args=image.id. LI, A, etc. son web2py helpers que se asignan a las correspondientes etiquetas HTML. Sus argumentos sin nombre se interpretan como objetos a serializar y se incluirán en innerHTML de la etiqueta. Argumentos con nombre que comienzan con un guión abajo (por ejemplo _href) se interpretan como atributos de la etiqueta, pero sin el carácter de subrayado. Por ejemplo _href es el atributo href, _class es el atributo class, etc.

Como ejemplo, la siguiente declaración:

{{=LI(A('something', _href=URL('show', args=123))}}

Se ofrece como:

1
<li><a href="/images/default/show/123">something</a></li>

Un montón de helpers (INPUT, TEXTAREA, OPTION y SELECT) también soportan algunos atributos con nombres especiales que no comienzan con subrayado (value, y requires). Ellos son importantes para construir formas específicas que y serán discutidos después.

Se regresa a la página edit. Esta ahora indica que “default.py exposes index”. Si se le da clic a “index”, se puede visitar la nueva página creada:

http://127.0.0.1:8000/images/default/index

la cual se ve:

MVC

Si se le dá clic en el enlace de el nombre de la imagen, se redirecciona a:

http://127.0.0.1:8000/images/default/show/1

Y esto produce un error, dado que no se ha creado todavía una acción llamada “show” en el controlador “default.py”.

Editemos el controlador “default.py” y reemplacemos su contenido con:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def index():
    images = db().select(db.image.ALL, orderby=db.image.title)
    return dict(images=images)
def show():
    image = db(db.image.id==request.args(0)).select().first()
    form = SQLFORM(db.comment)
    form.vars.image_id = image.id
    if form.accepts(request.vars, session):
       response.flash = 'your comment is posted'
    comments = db(db.comment.image_id==image.id).select()
    return dict(image=image, comments=comments, form=form)
def download():
    return response.download(request, db)

El controlador contiene dos acciones: “show” y “download”. La acción “show”selecciona la imagen con el id analizado por los agumentos del requerimiento y todo los comentarios relacionados con la imagen. “show”, entonces, pasa todo a la vista “default/show.html”.

La imagen id referenciada por:

1
URL('show', args=image.id)

En “default/index.html”, puede ser accedida como: request.args(0) desde la acción “show”.

La acción”download” espera un nombre de archivo en request.args(0), construye una ruta a la ubicación donde se supone que ese archivo debe estar, y lo envía de vuelta al cliente. Si el archivo es demasiado grande, se transmite el archivo sin incurrir en ninguna sobrecarga de la memoria.

Note las siguientes declaraciones:

  • La línea 7 crea una forma SQLFORM de inserción para la tabla db.comment usando sólo los campos especificados.

  • La línea 8 establece el valor por el campo de referencia, este no forma parte de la forma de entrada, ya que no está en la lista de los campos especificados arriba.

  • Línea 9 procesa la forma enviada (las variables de la forma enviadas están en en request.vars) en la sesión actual (la sesión se utiliza para evitar duplicaciones, y para reforzar la navegación). Si las variables de la forma presentada son validos, el nuevo comentario se añade en la tabla db.comment, de lo contrario la forma se modifica para incluir mensajes de error (por ejemplo, si la dirección de correo electrónico del autor no es válida). !Todo esto se hace en la línea 9!.

  • La línea 10 sólo se ejecuta si la forma es aceptada, después de que el registro se inserta en la tabla de base de datos. response.flash es una variable web2py que se muestra en las vistas y se utiliza para notificar al usuario de que algo pasó.

  • La línea 11 selecciona todos los comentarios que hacen referencia a la imagen actual.

    La acción “download” que ya se ha definido en el controlador “default.py” de la aplicación de andamiaje.

La acción “download” no devuelve un diccionario, por lo que no necesita un punto de vista. La acción “show”, sin embargo, debe tener una vista, por lo que se regresasa a admin a crear una nueva vista llamada “default/show.html”.

Editar este nuevo archivo y reemplazar su contenido con el texto siguiente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{{extend 'layout.html'}}
<h1>Image: {{=image.title}}</h1>
<center>
<img width="200px"
     src="{{=URL('download', args=image.file)}}" />
</center>
{{if len(comments):}}
  <h2>Comments</h2><br /><p>
  {{for comment in comments:}}
    <p>{{=comment.author}} says <i>{{=comment.body}}</i></p>
  {{pass}}</p>
{{else:}}
  <h2>No comments posted yet</h2>
{{pass}}
<h2>Post a comment</h2>
{{=form}}

Esta vista despliega la image.file llamando la acción “download”dentro de la etiqueta <img ... /> . Si hay comentarios, se hace un ciclo sobre ellos para que cada uno sea visto.

Aqui está como todo aparecerá para el visitante:

MVC

Cuando un visitante envía un comentario usando esta página, el comentario se guarda en la base de datos y se agrega al final de la página.

Agregando CRUD

web2py también provee una CRUD API (Crear/Leer/Actualizar/Borrar por su acrónimo en inglés) que simplifica las formas aún mas. Par usar CRUD es necesario definirlo en alguna parte, tal como en el módulo “db.py”:

1
2
from gluon.tools import Crud
crud = Crud(globals(), db)

Estas dos líneas están ya en la aplicación de andamiaje.

El objeto crud provee métodos de alto nivel, por ejemplo:

1
form = crud.create(table)

Y puede ser usado para reemplazar el patrón de programación:

1
2
3
4
form = SQLFORM(table)
if form.accepts(request.post_vars,session):
   session.flash = '...'
   redirect('...')

Aqui, se reescribe la acción “show”previa usando crud y haciendo algunas mejoras:

1
2
3
4
5
6
7
8
def show():
    image = db.image(request.args(0)) or redirect(URL('index'))
    db.comment.image_id.default = image.id
    form = crud.create(db.comment,
                       message='your comment is posted',
           next=URL(args=image.id))
    comments = db(db.comment.image_id==image.id).select()
    return dict(image=image, comments=comments, form=form)

Observerse, en principio que se ha usado la sintáxis:

1
db.image(request.args(0)) or redirect(...)

Para traer el registro requerido. Ya que table(id) no retorna nada si el registro no se encuentra, se puede usar or redirect(...) en este caso en una línea.

El argumento next de crud.create es la URL a la que se redirecciona después que la forma es aceptada. El argumento message es aquel que se verá luego de la aceptación. Puede leerse mas sobre CRUD en el capítulo 7.

Agregando autentificación

La aplicación API web2py para Control de Acceso basado en Roles es bastante sofisticada, pero por ahora nos limitaremos a restringir acceso a la acción show a usuarios autentificados, difiriendo una discusión mas detallada para el Capítulo 8.

Para limitar el acceso a usuarios autorizados, se necesitan completar tres pasos. En un modelo, por ejemplo “db.py”, necesita añadirse:

1
2
3
from gluon.tools import Auth
auth = Auth(globals(), db)
auth.define_tables()

En nuestro controlador, se necesita agregar una acción:

1
2
def user():
    return dict(form=auth())

Esto es suficiente para permitir la entrada al sistema, registrarse, salir del sistema, etc. El diseño por defecto también nos mostrará opciones a las páginas correspondientes en la esquina superior derecha.

MVC

Las funciones que se quieren restringir pueden ahora decorarse, por ejemplo

@auth.requires_login()
def show():
    image = db.image(request.args(0)) or redirect(URL('index'))
                     db.comment.image_id.default = image.id
    form = crud.create(db.comment, next=URL(args=image.id),
                       message='your comment is posted')
    comments = db(db.comment.image_id==image.id).select()
    return dict(image=image, comments=comments, form=form)

Cualquier intento para accesar:

http://127.0.0.1:8000/images/default/show/[image_id]

requerira una entrada al sistema. Si el usuario no ha entrado al sistema, se verá redireccionado a

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

MVC

La función user también expone, entre otras las siguientes acciones:

http://127.0.0.1:8000/images/default/user/logout

http://127.0.0.1:8000/images/default/user/register

http://127.0.0.1:8000/images/default/user/profile

http://127.0.0.1:8000/images/default/user/change_password

http://127.0.0.1:8000/images/default/user/request_reset_password

http://127.0.0.1:8000/images/default/user/retrieve_username

http://127.0.0.1:8000/images/default/user/retrieve_password

http://127.0.0.1:8000/images/default/user/verify_email

http://127.0.0.1:8000/images/default/user/impersonate

http://127.0.0.1:8000/images/default/user/not_authorized

Ahora, un usuario que visita por primera vez necesita registrarse para poder entrar al sistema y leer o agregar comentarios.

Ambos, el objeto auth y la función user están ya definidos en la aplicación de andamiaje. El objeto auth es altamente personalizable y puede manejar verificación de email, aprobar el registro, CAPTCHA, y alternar métodos de entrada a través de plugins.

Configurando el diseño

Se puede configurar el diseño por defecto editando “views/layout.html”, pero puede configurarse también sin editarse el HTML. De hecho, la hoja de estilo “static/base.css” está bien documentada y descrita en el Capítulo 5. Se pueden cambiar el color, las columnas, el tamaño, los bordes, el fondo sin editar el HTML. Si se quiere editar el menú, el título o el subtítulo, esto puede hacerse en cualquiera de los archivos model. La app andamio, determina valores por defecto de estos parámetros en el archivo “models/menu.py”:

1
2
3
4
5
6
response.title = request.application
response.subtitle = T('customize me!')
response.meta.author = 'you'
response.meta.description = 'describe your app'
response.meta.keywords = 'bla bla bla'
response.menu = [ [ 'Index', False, URL('index') ] ]

Un Wiki

En esta sección, se construye un wiki, a partir de cero y sin utilizar la funcionalidad extendida proporcionada por el plugin_wiki que se describe en el capítulo 13. El visitante podrá crear páginas, buscar en ellas (por título), y editarlas. El visitante también podrá enviar comentarios (exactamente como en las aplicaciones anteriores), así como publicar documentos (como archivos adjuntos a las páginas) y vincularlos a ellas. Por convenio, se adopta la sintaxis Markmin de nuestra sintaxis wiki. Se implementará también una página de búsqueda con Ajax, un feed RSS para las páginas, y un manejador para buscar las páginas a través de XML-RPC 46.

El diagrama siguiente, enumera las acciones que se necesitan para implementar y los enlaces que se intentan construir entre ellos.

MVC

Se comienza creando una nueva aplicación andamio llamada “mywiki”.

El modelo debe contener tres tablas: page (página), comment (comentario) y document (documento). Tanto comment como document referenciam page porque pertenecen a ella. Un documento contiene un campo archivo del tipo upload como en la aplicacion images anterior.

Aqui el modelo completo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
db = DAL('sqlite://storage.sqlite')
from gluon.tools import *
auth = Auth(globals(),db)
auth.define_tables()
crud = Crud(globals(),db)
db.define_table('page',
    Field('title'),
    Field('body', 'text'),
    Field('created_on', 'datetime', default=request.now),
    Field('created_by', db.auth_user, default=auth.user_id),
    format='%(title)s')
db.define_table('comment',
    Field('page_id', db.page),
    Field('body', 'text'),
    Field('created_on', 'datetime', default=request.now),
    Field('created_by', db.auth_user, default=auth.user_id))
db.define_table('document',
    Field('page_id', db.page),
    Field('name'),
    Field('file', 'upload'),
    Field('created_on', 'datetime', default=request.now),
    Field('created_by', db.auth_user, default=auth.user_id),
    format='%(name)s')
db.page.title.requires = IS_NOT_IN_DB(db, 'page.title')
db.page.body.requires = IS_NOT_EMPTY()
db.page.created_by.readable = db.page.created_by.writable = False
db.page.created_on.readable = db.page.created_on.writable = False
db.comment.body.requires = IS_NOT_EMPTY()
db.comment.page_id.readable = db.comment.page_id.writable = False
db.comment.created_by.readable = db.comment.created_by.writable = False
db.comment.created_on.readable = db.comment.created_on.writable = False
db.document.name.requires = IS_NOT_IN_DB(db, 'document.name')
db.document.page_id.readable = db.document.page_id.writable = False
db.document.created_by.readable = db.document.created_by.writable = False
db.document.created_on.readable = db.document.created_on.writable = False

Se edita el controlador “default.py” y se crean las siguientes acciones:

  • index: lista de todas la páginas wiki
  • create: publicar otra página wiki
  • show: mostrar una página wiki y sus comentarios, anexar dichos comentarios
  • edit: editar una página ya existente
  • documents: manejar los documentos anexados a una página
  • download: bajar un documento (igual que en ejemplo images)
  • search: desplegar un cuadro de búsqueda, via través de una llamada Ajax, retornar todos los títulos que coincidan con los tipos del visitante.
  • bg_find: la función de llamada Ajax. Retorna el HTML que se inserta en la máquina de búsqueda mientras el visitante escribe.

Aquí el controlador “default.py”:

def index():
    """ this controller returns a dictionary rendered by the view
        it lists all wiki pages
        >>> index().has_key('pages')
        True
        """
        pages = db().select(db.page.id,db.page.title,orderby=db.page.title)
        return dict(pages=pages)
@auth.requires_login()
def create():
    "creates a new empty wiki page"
    form = crud.create(db.page, next = URL('index'))
    return dict(form=form)
def show():
    "shows a wiki page"
    this_page = db.page(request.args(0)) or redirect(URL('index'))
    db.comment.page_id.default = this_page.id
    form = crud.create(db.comment) if auth.user else None
    pagecomments = db(db.comment.page_id==this_page.id).select()
    return dict(page=this_page, comments=pagecomments, form=form)
@auth.requires_login()
def edit():
    "edit an existing wiki page"
    this_page = db.page(request.args(0)) or redirect(URL('index'))
    form = crud.update(db.page, this_page,
         next = URL('show', args=request.args))
    return dict(form=form)
@auth.requires_login()
def documents():
    "lists all documents attached to a certain page"
    this_page = db.page(request.args(0)) or redirect(URL('index'))
    db.document.page_id.default = this_page.id
    form = crud.create(db.document)
    pagedocuments = db(db.document.page_id==this_page.id).select()
    return dict(page=this_page, documents=pagedocuments, form=form)
def user():
    return dict(form=auth())
def download():
    "allows downloading of documents"
    return response.download(request, db)
def search():
    "an ajax wiki search page"
    return dict(form=FORM(INPUT(_id='keyword',_name='keyword',
             _onkeyup="ajax('bg_find', ['keyword'], 'target');")),
             target_div=DIV(_id='target'))
def bg_find():
    "an ajax callback that returns a <ul> of links to wiki pages"
    pattern = '%' + request.vars.keyword.lower() + '%'
    pages = db(db.page.title.lower().like(pattern))\
      .select(orderby=db.page.title)
    items = [A(row.title, _href=URL('show', args=row.id)) \
            for row in pages]
    return UL(items).xml()

Las líneas 2-6 ofrecen un comentario de la acción index. Las líneas 4-5 dentro del comentario se interpretan en python como código prueba (doctest). Las pruebas se pueden ejecutar a través de la interfaz admin. En este caso las pruebas comprueban que la acción index se ejecuta sin errores.

Las líneas 18, 27, 35 tratan de buscar un registro page con el id en request.args(0).

Las líneas 13, 20 y 37 definen y procesan formas creadas, por una nueva página y un comentario nuevo además de un nuevo documento, respectivamente.

La línea 28 define y procesa una formade actualización de una página wiki.

Cierta magia que sucede en la línea 51. El atributo onkeyup de la etiqueta INPUT “keyword” se define. Cada vez que el visitante suelta una tecla, el código JavaScript insertado en el atributo onkeyup se ejecuta en el cliente. Aquí está el código JavaScript:

ajax('bg_find', ['keyword'], 'target');

ajax es una función de JavaScript definida en el archivo “web2py_ajax.html”, que es incluida por el diseño por defecto “layout.html”. Esta toma tres parámetros: el URL de la acción que realiza la llamada síncrona (“bg_find”), una lista de los id de las variables que se enviará a la llamada ([“keyword”]), y el ID donde la respuesta tiene que ser insertada (“target”).

Tan pronto como algo se escriba en el cuadro de búsqueda y se suelte una tecla, el cliente llama al servidor y envía el contenido del campo “keyword”, y, cuando el servidor responde, la respuesta está incrustada en la misma página como el innerHTML de la etiqueta ‘target’.

La etiqueta ‘target’ es una DIV definida en la línea 52. Podría haber sido definida en la vista también.

Aquí está el código para la vista “default/create.html”:

1
2
3
{{extend 'layout.html'}}
<h1>Create new wiki page</h1>
{{=form}}

Si se visita la página create, se verá lo siguiente:

MVC

aquí el código por la vista “default/index.html”:

1
2
3
4
5
6
7
{extend 'layout.html'}}
<h1>Available wiki pages</h1>
[ {{=A('search', _href=URL('search'))}} ]<br />
<ul>{{for page in pages:}}
     {{=LI(A(page.title, _href=URL('show', args=page.id)))}}
{{pass}}</ul>
[ {{=A('create page', _href=URL('create'))}} ]

Este genera la siguiente página:

MVC

aquí el código para la vista “default/show.html”:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{{extend 'layout.html'}}
<h1>{{=page.title}}</h1>
[ {{=A('edit', _href=URL('edit', args=request.args))}}
| {{=A('documents', _href=URL('documents', args=request.args))}} ]<br />
{{=MARKMIN(page.body)}}
<h2>Comments</h2>
{{for comment in comments:}}
  <p>{{=db.auth_user[comment.created_by].first_name}} on
  {{=comment.created_on}}
           says <I>{{=comment.body}}</i></p>
{{pass}}
<h2>Post a comment</h2>
{{=form}}

Si se desea usar la sintaxis markdown en vez de la sintaxis markmin:

1
from gluon.contrib.markdown import WIKI

Y se usa el helper WIKI en lugar del MARKMIN. Alternativamente, puede escojerse aceptar HTML crudo en vez de sintaxis markmin. En este caso se reemplazaría:

1
{{=MARKMIN(page.body)}}

con:

1
{{=XML(page.body)}}

(de tal forma que el XML no se escape, como es el comportamiento por defecto de web2py)

Esto puede hacerse mejor con:

1
{{=XML(page.body, sanitize=True)}}

Asignando sanitize=True, se le dice a web2py que deje fugar etiquetas XML no seguras, tal como “<script>”, y asi prevenir vulnerabilidades XSS.

Ahora si, desde la página index, se hace clic a un título de página, puede verse lo que se ha creado:

MVC

aquí el código por la vista “default/edit.html”:

1
2
3
4
{{extend 'layout.html'}}
<h1>Edit wiki page</h1>
[ {{=A('show', _href=URL('show', args=request.args))}} ]<br />
{{=form}}

Este genera una página que se ve casi igual a la página create.

Aquí el código de la vista “default/documents.html”:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{{extend 'layout.html'}}
<h1>Documents for page: {{=page.title}}</h1>
[ {{=A('show', _href=URL('show', args=request.args))}} ]<br />
<h2>Documents</h2>
{{for document in documents:}}
   {{=A(document.name, _href=URL('download', args=document.file))}}
   <br />
{{pass}}
<h2>Post a document</h2>
{{=form}}

Si, desde la página “show”, se le dá clic a documentos, se pueden manejar ahora los documentos anexados a la página.

MVC

Finalmente, este es el código por la vista “default/search.html”:

1
2
3
4
{{extend 'layout.html'}}
<h1>Search wiki pages</h1>
[ {{=A('listall', _href=URL('index'))}}]<br />
{{=form}}<br />{{=target_div}}

El cual genera la siguiente forma de búsqueda Ajax:

MVC

Se puede también tratar de llamar la acción directamente visitando, por ejemplo el siguiente URL:

http://127.0.0.1:8000/mywiki/default/bg_find?keyword=wiki

Si se mira en la página fuente, se vé el HTML retornado por la llamada:

1
<ul><li><a href="/mywiki/default/show/4">I made a Wiki</a></li></ul>

Generar un feed RSS de las páginas almacenadas usando web2py es fácil porque web2py incluye gluon.contrib.rss2. Solo hay que anexar la siguiente acción al controlador por defecto:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def news():
    "generates rss feed form the wiki pages"
    pages = db().select(db.page.ALL, orderby=db.page.title)
    return dict(
       title = 'mywiki rss feed',
       link = 'http://127.0.0.1:8000/mywiki/default/index',
       description = 'mywiki news',
       created_on = request.now,
       items = [
          dict(title = row.title,
               link = URL('show', args=row.id),
               description = MARKMIN(row.body).xml(),
               created_on = row.created_on
               ) for row in pages])

Y cuando se visita la página

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

se ve el feed (la salida exacta depende del lector de feeds). Observe que el dict se convierte automáticamente a RSS, gracias a la extensión .rss en el URL.

MVC

web2py también incluye feedparser para leer feeds de terceros.

Finalmente, agreguemos un manejador XML-RPC que permita buscar en el wiki a través de programación:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
service=Service(globals())
@service.xmlrpc
def find_by(keyword):
    "finds pages that contain keyword for XML-RPC"
    return db(db.page.title.lower().like('%' + keyword + '%'))\
        .select().as_list()

def call():
    "exposes all registered services, including XML-RPC"
    return service()

Aquí el manejador simplemente publica (via XML-RCP), las funciones especificadas en la lista. En este caso, find_by. find_by no es una acción (porque toma un argumento). Esta busca la base de datos con .select() y luego extrae los registros como una lista con .response y retorna la lista.

Aquí un ejemplo de como acceder el manejador XML-RCP desde un programa Python externo.

1
2
3
4
5
>>> import xmlrpclib
>>> server = xmlrpclib.ServerProxy(
    'http://127.0.0.1:8000/mywiki/default/call/xmlrpc')
>>> for item in server.find_by('wiki'):
        print item.created_on, item.title

El manejador puede ser accedido desde muchos otros lenguajes de programación que entienden XML-RCP, incluyendo C, C++, C# y Java.

Más sobre admin

La interfaz administrativa provee funcionalidad adicional que revisamos brevemente a continuación.

site

Está página lista todas las aplicaciones instaladas. Hay dos formas en el fondo.

La primera de ellas permite crear una aplicación nueva especificando su nombre.

La segunda forma permite bajar una aplicación existente bien sea de un archivo local o de un URL remoto. Cuando una aplicación es bajada, es necesario especificar su nmbre. Este puede ser su nombre original, pero no necesariamente. Esto permite instalar múltiples copias de la misma aplicación. Se puede tratar, por ejemplo, de bajar el sistema de administración de contenido KPAX desde:

http://web2py.com/appliances/default/download/app.source.221663266939....

Aplicaciones que han sido bajadas pueden ser archivos .tar (convención vieja) y archivos .w2p (convención nueva). Las últimas son archivos gzipped tar. Se pueden descomprimir manualmente con tar zxvf [nombre del archivo] aunque esto nunca es necesario.

MVC

Después de una descarga exitosa, web2py despliega la suma de control del archivo bajado. Puede usarse para verificar que la fila no se corrompió durante la descarga. El nombre KPAX aparecerá en la lista de aplicaciones instaladas.

Se hace clic en el nombre KPAX en admin para crearlo y correrlo.

MVC

Los archivos de aplicación están almacenados como archivos web2py (tar gzipped), pero la intención no es que se compriman o no manualmente, web2py lo hace por u

Por cada aplicación el site permite:

  • Desinstalar la aplicación.

  • Ir a la página about (leer abajo).

  • edit (leer abajo).

  • Ir a la página errors (leer abajo).

  • Limpiar archivos temporales (archivos de sesiones, errores y cache.disk).

  • Comprimir todo. Esto retorna un archivo tar que contiene una copia completa de la aplicación. Se sugiere limpiar primero los archivos temporales antes de comprimir una aplicación.

  • Compilar la aplicación. Si no hay errores, esta opción compilará en bytecode todos los modelos, conroladores y vistas. Ya que las vistas pueden extenderse e incluir otras vistas en un árbol, antes de la compilación bytecode, el árbol de la vista para cada controlador se colapsa en un solo archivo. El efecto neto es que una aplicación compilada bytecode es mas rápida, porque no hay mas más revisiones de formatos, o substituciones de cadenas ocurriendo en tiempo de ejecución.

  • Paquete compilado. Esta opción solo está presente en aplicaciones compiladas bytecode. Permite empacar la aplicaciones in código fuente para distribución como código cerrado. Observe que python (tal como otros lenguajes de programación) puede técnicamente ser descompilado; por lo tanto compilar no ofrece protección completa del código fuente. Sin embargo, decompilar puede ser difícil e ilegal.

  • Remover compilaciones.. Esto simplemente remueve los modelos, vistas y controladores compilados bytecode de la aplicación. En caso de que la aplicación haya sio empaquetad en código fuente o disñada en forma local, no hay peligro en remover los archivos compilados bytecode, la aplicación seguirá trabajando. Si la aplicación fué instalada desde un archivi empaquetado compilado, esto no es muy seguro, porque no hay código fuente a donde regresar, y la palicación puede que no trabaje más.

    Toda la funcionalidad disponible desde la página admin en web2py se puede acceder mediante programación via API definidas en el módulo gluon/admin.py. Simplemente abra un shell python e importe este módulo.

about

El tabulador about permite editar la descripción de la aplicación y su licencia. Están son escritas respectivamente en los archivos ABOUT y LICENSE en la carpeta de aplicaciones.

MVC

Se puede usar la sintaxis MARKMIN, o gluon.contrib.markdown.WIKI para estos archivos tal como se describe en la referencia. 29

edit

La página edit ha sido ya usada en este capítulo. Aquí quiero señalar unas funcionalidades adicionales de esta página:

  • Al darle clic a un nombre de archivo, puede verse el contenido con la sintaxis resaltada.I
  • Al darle clic a edit, el archivo puede editarse usando una interface de red.I
  • Si se hace clic en delete, el archivo puede borrarse (permanentemente).
  • Si se hace clic en test, web2py ejecutará pruebas. Estas pruebas son escritar por el desarrollador usando Python doctests, y cada función debe tener sus propias pruebas.
  • Pueden agregarse archivos de lenguajes, buscar en la aplicación todas las cadenas, y editar traducciones de cadenas a través de la interface en la red.
  • Si los archivos estáticos están organizados en carpetas y sub-carpetas, la jerarquía de carpetas puede ser alternada dándole clic a el nombre de la carpeta.

Esta imagen muestra la salida de la página de prueba para la aplicación welcome.

MVC

La imagen de abajo muestra el tabulador de lenguajes para la aplicación welcome.

MVC

La próxima imagen muestra comoe ditar un archivo de lenguaje, en este caso el lenguaje “it” (italiano) para la aplicación welcome.

MVC

shell

Al hacer clic en el enlace “shell” bajo el tabulador de controladores en edit, web2py abrirá un shell Phyton basado en al red y ejecutará los modelos para la aplicación siguiente. Este permitirá hablar intercativamente con la aplicación.

MVC

crontab

También, bajo el tabulador de los controladores en edit hay un enlace “crontab”. Al darle clic a este enlace será posible editar el archivo crontab de web2py. Este sigue la misma sintaxis como el crontab unix pero no se basa en unix. De hecho, solo requiere web2py, y trabaja en Windows. Permite registrar acciones que necesitan ser ejecutadas en el fondo a horas programadas. Para mas información al respecto, ver el próximo capítulo.

errors

Cuando se programa en web2py, inevitablemente se cometerán errores y se introducirán bugs. Web2py ayuda de dos formas: 1) Permite crear pruebas por cada función que pueden ser ejecutadas en el browser desde la página edit; y 2) Cuando un error aparece solo, se emite un boleto al visitante y este error se registra.

Se introduce a propósito un error en la aplicación images, tal como se ve a continuación:

1
2
3
4
def index():
    images = db().select(db.image.ALL,orderby=db.image.title)
    1/0
    return dict(images=images)

Cuando se accede la acción index, se emite el siguiente boleto:

MVC

Solo el administrador puede acceder el boleto:

MVC

El boleto se muestra la ejecución de las llamadas mas recientes, y el contenido del archivo que causó el problema. Si el error se produce en una vista, web2py muestra la vista conviertida de HTML en código Python. Esto permite identificar fácilmente la estructura lógica del archivo.

Tenga en cuenta que en todas partes donde admin muestra código de sintaxis resaltado (por ejemplo, en los informes de errores, las palabras clave web2py se muestran en naranja). Si se hace clic en una palabra clave web2py, se le redirige a una página de documentación sobre la palabra clave.

Si usted fija la división por cero error en la acción index e introduce una en la vista index:

1
2
3
4
5
6
7
8
{{extend 'layout.html'}}
<h1>Current Images</h1>
<ul>
{{for image in images:}}
{{1/0}}
{{=LI(A(image.title, _href=URL("show", args=image.id)))}}
{{pass}}
</ul>

Se obtiene el siguiente boleto:

MVC

Se observa que web2py ha convertido la vista de HTML a un archivo Python, y el error descrito en el boleto se refiere al código Python generado y NO a el archivo vista original.

Se produce el siguiente boleto:

MVC

Esto puede parecer confuso al principio, pero en práctica hace la eliminación de errores mas fácil, porque la sangría de Python resalta la estructura lógica del código que se insertó en las vistas.

El código se muestra en el fondo de la misma página.

Todos los boletos se listan bajo admin en la página errors para cada aplicación:

MVC

Mercurial

Si se está ejecutando de la fuente y se tiene la versión Mercurial de las librerías de control instalada:

easy_install mercurial

Entonces la interfaz administrativa muestra un menú adicional llamado “mercurial”. Este crea automáticamente un repositorio Mercurial local para la aplicación. Al presionar el botón “commit” se asigna a la aplicación actual.

Este elemento es experimental y será mejorado en el futuro.

Mas en appadmin

appadmin no está destinada a ser expuesta al público. Está diseñada para ayudar a proporcionar un acceso fácil a la base de datos. Se compone de solo dos archivos: un controlador “appadmin.py” y una vista “appadmin.html” que son utilizados por todas las acciones en el controlador.

El controlador appadmin es relativamente pequeño y fácil de leer, ofreciendo un ejemplo en el diseño de una interfaz de base de datos.

appadmin muestra cuales bases de datos están disponibles y las tablas que existen en cada base de datos. Se pueden insertar registros y la lista de todos los registros de cada tabla por separado. appadmin pagina la salida de 100 registros a la vez.

Una vez que un conjunto de registros está seleccionado, el encabezado de las páginas de los cambios, lo que le permite actualizar o eliminar los registros seleccionados.

Para actualizar los registros, se introduce una asignación SQL en el campo de cadena del Query:

1
title = 'test'

donde los valores de cadena debe ir entre comillas simples. Campos múltiples pueden ser separados por comas.

Para borrar un registro, haga clic en la casilla correspondiente para confirmar que está seguro.

appadmin también puede realizar enlaces si el SQL FILTER contiene una condición SQL que involucra a dos o más tablas. Por ejemplo, probemos:

1
db.image.id == db.comment.image_id

Web2py pasa esto a lo largo del DAL, y entiende que el query está enlazando dos tablas, por lo tanto ambas tablas se seleccionan con un INNER JOINT. He aquí la salida:

MVC

Si hace clic en el número de un campo id, se obtiene una página de edición del registro con el id correspondiente.

Si hace clic en el número de un campo de referencia, se obtiene una página de edición del registro referenciado.

No se pueden actualizar o eliminar registros seleccionado por un enlace, porque implican registros de varias tablas y esto sería ambiguo.

Contenidos

Tema anterior

El lenguaje Python

Próximo tema

El Núcleo

Envíe sus comentarios o correcciones a comunidad@latinuxpress.com

Patrocinado por