Otras Recetas

Actualización

En la página del sitio de la interfaz administrativa hay un botón “upgrade now”. En caso de que esto no sea factible o no funcione (por ejemplo debido a un problema de bloqueos de archivos), actualizar web2py manualmente es muy fácil.

Simplemente descomprima la última versión de web2py encima de la instalación anterior.

Ésto actualizará todas las librerías así como las aplicaciones admin, examples, welcome. También creará un nuevo archivo vacío “NEWINSTALL”. Luego de reiniciar, web2py borrará este archivo vacío y empacará la aplicación welcome en “welcome.w2p” que será usada como la nueva aplicación de andamiaje.

web2py no actualiza ningún otro archivo en las demás aplicaciones existentes.

Cómo distribuir sus distribuciones como binarios

Es posible empaquetar su aplicación junto con la distribución binaria de web2py y distribuirlas juntas. La licencia permite esto siempre que usted aclare en la licencia de su aplicación que la está entregando junto con web2py y agregue un enlace a web2py.com.

Aquí explicamos como hacerlo en Windows:

  • Cree su aplicación como de costumbre
  • Usando admin, compile en bytecode su aplicación (un click)
  • Usando admin, empaque su aplicación compilada (otro click)
  • Cree una carpeta “myapp”
  • Descargue una distribución binaria windows de web2py
  • Descomprima dicha distribución en la carpeta “myapp” e iniciela (dos clicks)
  • Cargue, usando admin, la anterior aplicación empacada y compilada con el nombre “init” (un click)
  • Cree un archivo “myapp/start.bat” que contenga “cd web2py; web2py.exe”
  • Cree un archivo “myapp/license” que contenga una licencia para su aplicación y asegurese que establezca que está siendo “distribuida con una copia no modificada de web2py desde web2py.com”
  • Comprima la carpeta myapp en un archivo “myapp.zip”
  • Distribuya y/o venda “myapp.zip”

Cuando los usuarios descompriman “myapp.zip” y le den click a “run” ellos verán su aplicación en vez de la aplicación “welcome”. No hay requerimientos del lado del usuario, ni siquiera tener Python pre-instalado.

Para los binarios de Mac el proceso es el mismo pero no hay necesidad del archivo “bat”.

Obteniendo una URL externa

Python incluye la librería urllib para obtener urls:

1
2
import urllib
page = urllib.urlopen('http://www.web2py.com').read()

La mayoría de las veces esto está bien, pero el módulo urllib no trabaja en el Motor de Aplicaciones de Google. Google proporciona una API diferente para descargar URLs que trabajan sólo con GAE. Con el fin de hacer que su código sea portátil, web2py inlcuye una función fetch que trabaja en GAE así como en otras instalaciones Python:

1
2
from google.tools import fetch
page = fetch('http://www.web2py.com')

Fechas amigables

Comúnmente resulta útil representar fechas y horas no como “2009-07-25 14:34:56” sino como “hace un año”. web2py proporciona una funciónm de utilidad para ésto:

1
2
3
4
import datetime
d = datetime.datetime(2009,7,25,14,34,56)
from gluon.tools import prettydate
pretty_d = prettydate(d,T)

El segundo argumento (T) debe ser pasado para permitir la internacionalización de la salida.

Geocodificación

Si usted necesita convertir una dirección (por ejemplo:”243 S Wabash Ave, Chicago, IL, USA”) en coordenadas geográficas (latitud y longitud), web2py provee una función para hacerlo.

1
2
3
from gluon.tools import geocode
address = '243 S Wabash Ave, Chicago, IL, USA'
(latitude, longitude) = geocode(address)

La función geocode requiere una conexión de red y se conecta al servicio de geocodificación de Google para lograr la misma. La función retorna (0,0) en caso de fallo. Note que el servicio de geocodificación de Google limita el número de solicitudes, así que debe verificar su acuerdo de servicio. La función geocode está contruida encima de la función fetch y por lo tanto trabaja con GAE.

Paginación

Esta receta es un truco útil para minimizar el acceso a base de datos en el caso de la paginación, es decir, cuando usted necesita mostrar una lista de filas de una base de datos pero quiere distribuir las filas en múltiples páginas.

Comience creando una aplicación primes que guarda los primero 1000 números primos en una base de datos.

Aquí está el modelo db.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
db = DAL('sqlite://primes.db')
db.define_table('prime',Field('value','integer'))
def isprime(p):
    for i in range(2,p):
        if p%i==0: return False
    return True
if len(db().select(db.prime.id))==0:
   p=2
   for i in range(1000):
       while not isprime(p): p+=1
       db.prime.insert(value=p)
       p+=1

Ahora cree una acción list_items en el controlador “default.py” que lea así:

1
2
3
4
5
6
7
def list_items():
    if len(request.args): page=int(request.args[0])
    else: page=0
    items_per_page=20
    limitby=(page*items_per_page,(page+1)*items_per_page+1)
    rows=db().select(db.prime.ALL,limitby=limitby)
    return dict(rows=rows,page=page,items_per_page=items_per_page)

Note que este código selecciona un ítem más del que necesita, 20+1. El elemento extra le dice a la vista si hay o no una siguiente página.

Aquí tenemos la vista “default/list_items.html”:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{{extend 'layout.html'}}

{{for i,row in enumerate(rows):}}
{{if i==items_per_page: break}}
{{=row.value}}<br />
{{pass}}

{{if page:}}
<a href="{{=URL(args=[page-1])}}">previous</a>
{{pass}}

{{if len(rows)>items_per_page:}}
<a href="{{=URL(args=[page+1])}}">next</a>
{{pass}}

De esta manera hemos logrado la paginación con una única selección por acción, y esa única selección solo selecciona, valga la redundancia, una fila más de la que necesitamos.

httpserver.log y el Formato del Archivo de Registros

El servidor web de web2py registra todas las solicitudes en un archivo llamado:

1
httpserver.log

en la raíz del directorio web2py. Un nombre de archivo y ubicación alternativa puede ser especificado por medio de las opciones de línea de comandos de web2py.

Las nuevas entradas son agregadas al final de cada archivo cada vez que se hace una solicitud. Cada línea luce así:

127.0.0.1, 2008-01-12 10:41:20, GET, /admin/default/site, HTTP/1.1, 200, 0.270000

El formato es:

1
ip, timestamp, method, path, protocol, status, time_taken

Donde

  • ip es la dirección IP del cliente que hizo la solicitud
  • timestamp es la fecha y hora de la solicitud en el formato ISO 8601, YYYY-MM-DDT HH:MM:SS
  • method es el método GET o POST
  • path es la ruta solicitada por el cliente
  • protocol es el protocolo HTTP usado para enviar al cliente, usualmente HTTP/1.1
  • statis es uno de los códigos de estatus HTTP 91
  • time_taken es la cantidad de tiempo que le tomó al servidor para procesar la solicitud, en segundos, si incluir los tiempos de carga/descarga.

En el repositorio de dispositivos 34, usted encontrará un dispositivo para el análisis de registros.

Este registro está por defecto deshabilitado al usar mod_wsgi debido a que sería lo mismo que el registro de Apache.

Llenando la Base de Datos con datos simulados

Con fines de pruebas es conveniente poder rellenar las tablas de las bases de datos con datos simulados. web2py incluye un clasificador Bayesiano ya entrenado para generar, para este propósito, texto simulado pero leible.

Aquí está la manera más simple de usarlo:

1
2
from gluon.contrib.populate import populate
populate(db.mytable,100)

Insertará 100 registros simulados en db.mytable. Tratará de hacerlo inteligentemente generando texto corto para campos de tipo cadena, texto más largo para campos de tipo texto, enteros, dobles, fechas, fechas y horas, horas, booleanos, etc. para los campos correspondientes. Tratará de respetar los requerimientos impuestos por los validadores. Para campos que contengan la palabra “name” tratará de generar nombres simulados. Para campos de referencia generará referencias válidas.

Si usted tiene dos tablas (A y B) donde B hace referencia a A, asegúrese de rellenar A primero y B después.

Debido a que el llenado es hecho en una transacción, no intente rellenar muchos registros a la vez, particularmente si hay referencias involucradas. En cambio, rellene 100 a la vez, confirme, y repita el ciclo.

1
2
3
for i in range(10):
    populate(db.mytable,100)
    db.commit()

Puede usar el clasificador Bayesiano para que aprenda algún texto y genere texto simulado que parezca similar pero que no debería tener sentido:

1
2
3
4
from gluon.contrib.populate import Learner, IUP
ell=Learner()
ell.learn('some very long input text ...')
print ell.generate(1000,prefix=None)

Enviar un SMS

Enviar mensajes SMS desde una aplicación web2py requiere un servicio de terceros que pueda transmitir los mensajes al destinatario. Usualmente este no es un servicio gratuito, pero cambia de país a país. Hemos tratado unos cuantos de esos servicios con poco éxito. Las compañías telefónicas bloquean correos que se originan desde éstos servicios ya que eventualmente terminan siendo utilizados como spam.

Una mejor manera es usar a las compañías de teléfonos para que ellas mismas transmitan el SMS. Cada compañía de teléfonos tiene una dirección de correo asociada de manera única a cada número de teléfono celular, el SMS puede ser enviado como un correo a ese número de teléfono.

web2py trae un módulo que ayuda en este proceso:

1
2
3
from gluon.contrib.sms_utils import SMSCODES, sms_email
email = sms_email('1 (111) 111-1111','T-Mobile USA (tmail)')
mail.sent(to=email,subject='test',message='test')

SMSCODES es un diccionario de asigna los nombres de las compañías de teléfonos más importantes al sufijo de la dirección de correo. La función sms_email toma un número de teléfono (como una cadena) y el nombre de una compañía de teléfonos y devuelve la dirección de correos del teléfono.

Aceptando pagos de Tarjetas de Crédito

web2py proporciona múltiples maneras para que su aplicación acepte pagos de tarjetas de créditos. Por ejemplo:

Plugin de Google Checkout

http://web2py.com/plugins/static/web2py.plugin.google_checkout.w2p

PayPal

http://www.web2pyslices.com/main/slices/take_slice/9

Authorize.net

Los primeros dos mecanismos mostrados arriba delegan el proceso de autenticación del beneficiario a un servicio externo. Mientras esta es la mejor solución en cuanto a seguridad (su aplicación no maneja ninguna información de la tarjeta de crédito) hace el proceso incómodo (el usuario debe iniciar sesión dos veces; por ejemplo, una vez con su aplicación, y otra con Google) y no permite que su aplicación maneje pagos recurrentes de una manera automatizada.

Hay veces donde usted necesita más control. Es por esta razón que proporcionamos integración desde el comienzo con la API de Authorize.net (el módulo fué desarrollado por John Conde y fué levemente modificado). Aquí hay un ejemplo del flijo de trabajo y todas las variables que se exponen:

 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
36
37
from gluon.contrib.AuthorizeNet import AIM
payment = AIM(login='cnpdev4289',
              transkey='SR2P8g4jdEn7vFLQ',
              testmod=True)
payment.setTransaction(creditcard, expiration, total, cvv, tax, invoice)
payment.setParameter('x_duplicate_window', 180) # three minutes duplicate windows
payment.setParameter('x_cust_id', '1324')       # customer ID
payment.setParameter('x_first_name', 'Agent')
payment.setParameter('x_last_name', 'Smith')
payment.setParameter('x_company', 'Test Company')
payment.setParameter('x_address', '1234 Main Street')
payment.setParameter('x_city', 'Townsville')
payment.setParameter('x_state', 'NJ')
payment.setParameter('x_zip', '12345')
payment.setParameter('x_country', 'US')
payment.setParameter('x_phone', '800-555-1234')
payment.setParameter('x_description', 'Test Transaction')
payment.setParameter('x_customer_ip', socket.gethostbyname(socket.gethostname()))
payment.setParameter('x_email', 'you@example.com')
payment.setParameter('x_email_customer', False)

payment.process()
if payment.isApproved():
    print 'Response Code: ', payment.response.ResponseCode
    print 'Response Text: ', payment.response.ResponseText
    print 'Response: ', payment.getResultResponseFull()
    print 'Transaction ID: ', payment.response.TransactionID
    print 'CVV Result: ', payment.response.CVVResponse
    print 'Approval Code: ', payment.response.AuthCode
    print 'AVS Result: ', payment.response.AVSResponse
elif payment.isDeclined():
    print 'Your credit card was declined by your bank'
elif payment.isError():
    print 'It did not work'
print 'approved',payment.isApproved()
print 'declined',payment.isDeclined()
print 'error',payment.isError()

Observe que el código de arriba usa una cuenta de prueba simulada. Usted necesita registrarse con Authorize.net (no es un servicio gratuito) y proporcionar su propio inicio de sesión, transkey, testmode=True or False al constructor AIM.

API Twitter

Aquí tenemos unos cuantos ejemplos rápidos de como enviar/recibir tweets. No se requieren librerías de terceros, ya que Twitter usa simples APIs RESTful.

He aquí un ejemplo de como enviar tweets:

1
2
3
4
5
6
7
8
def post_tweet(username,password,message):
    import urllib, urlib2, base64
    import gluon.contrib.simplejson as sj
    args= urllib.urlencode([('status',message)])
    headers={}
    headers['Authorization'] = 'Basic '+base64.b64encode(username+':'+password)
    request = urllib2.Request('http://twitter.com/statuses/update.json', args, headers)
    return  sj.loads(urllib2.urlopen(req).read())

Aquí un ejemplo de como recibir tweets:

1
2
3
4
5
6
7
def get_tweets():
    user='web2py'
    import urllib
    import gluon.contrib.simplejson as sj
    page = urllib.urlopen('http://twitter.com/%s?format=json' % user).read()
    tweets=XML(sj.loads(page)['#timeline'])
    return dict(tweets=tweets)

Para operaciones más complejas, revise la documentación del API de Twitter.

Streaming de Archivos Virtuales

Es común para atacantes maliciosos realizar escaneado a los sitios web en busca de vulnerabilidades. Ellos usan escáners de seguridad como Nessus para explorar los sitios web que tienen de objetivo en busca de códigos que son conocidos por tener vulnerabilidades. Un análisis del registro de un servidor web de una máquina escaneada o directamente en la base de datos de Nessus revela que la mayoría de las vulnerabilidades conocidas están en código PHP y ASP. Ya que estamos ejecutando web2py, no tenemos esas vulnerabilidades, pero igual seremos escaneados por ellos. Esto es muy molesto, así que nos gustaría responder a esos escaneos de vulnerabilidad y hacer entender al atacante que están perdiendo el tiempo.

Una posibilidad es redireccionar todas las solicitudes por .php, .asp, y cualquier otra solicitud sospechosa a una acción simulada que le responda al ataque manteniendo al atacante ocupado por un largo período de tiempo. Eventualmente el atacante se rendirá y no nos escaneará de nuevo.

Esta receta requiere dos partes.

Una aplicación dedicada llamada jammer con un controlador “default.py” como sigue:

1
2
3
class Jammer():
   def read(self,n): return 'x'*n
def jam(): return response.stream(Jammer(),40000)

Cuando esta acción es llamada, responde con una cadena infinita de datos llena de “x”-es.40000 caracteres a la vez.

El segundo ingrediente es un archivo “route.py” que redirecciona cuanquier solicitud que termine en .php, .asp, etc. (tanto en mayúsculas como en minúsculas) a este controlador.

1
2
3
route_in=(
 ('.*\.(php|PHP|asp|ASP|jsp|JSP)','jammer/default/jam'),
)

La primera vez que sea atacado puede ser que incurra en un poco de sobrecarga, pero nuestra experiencia dice que el atacante no lo intentará dos veces.

Jython

web2py normalmente se ejecuta en CPython (el intérprete de Python codificado en C), pero también puede ejecutarse en Jython (el intérprete de Python codificado en Java). Esto permite que web2py se ejecute en una infraestructura Java.

Aún cuando web2py se ejecuta Jython desde el inicio, hay algunos trucos involucrados en la configuración de Jython y en configurar zxJDBC (el adaptador de base de datos de Jython). Aquí están las instrucciones:

  • Descargue el archivo “jython_installer-2.5.0.jar” (ó 2.5.x) desde Jython.org

  • Instalelo:

    java -jar jython_installer-2.5.0.jar
  • Descargue e instale “zxJDBC.jar” desde 93
  • Descargue e instale el archivo “sqlitejdbc-v056.jar” desde 94
  • Agregue zxJDBC y sqlitejdbc a la CLASSPATH de java
  • Inicie web2py con Jython
/path/to/jython web2py.py

Al momento de escribir el libro sólo soportamos sqlite y postgres en Jython.