Instalando Python 2.7 y Django 1.3 en Dreamhost

1 sep

Dreamhost es genial para ejecutar sitios web desarrollados con Django. Nos dan acceso SSH que nos permite ejecutar los comandos de manage.py con facilidad; ejecutan Django con Passanger que es más rápido y usa menos memoria que mod_python; y por supuesto: recursos ilimitados, bajos costos y excelente soporte técnico.

Para ejecutar una aplicación Django en Dreamhost solo es necesario configurar el dominio para que utilice Passenger. Es cuestión de editar el dominio y seleccionar la caja que dice “Passenger (Ruby/Python apps only)” y asegurarse que la carpeta pública sea algo como /dominio.com/public/.

El panel de control configura el resto. Sin embargo, la versión de Python y Django que instalan en sus servidores está muy desactualizada (Python 2.4, Django 1.1 por defecto). Por suerte nos permiten compilar programas en el servidor así que instalar una versión moderna es extremadamente fácil.

Este es el proceso que yo utilizo para instalar versiones actualizadas de Python y Django en mis servidores ( sí, plural :P ). Pueden ir copiando y pegando línea por línea en la ventana SSH. Casi podría ser un script automatizado, excepto por que hay que salir y volver a entrar en cierta parte.

Para empezar, hay que conectarse por SSH a su servidor. En Windows pueden usar Putty, en Linux y MacOS ya deberían tener instalado el comando ssh que viene con OpenSSH.

Una vez conectados, vamos a crear una carpeta para descargar los instaladores.

mkdir ~/installers
cd ~/installers

Ahora vamos a descargar, compilar e instalar Python 2.7.2 en unos pocos comandos:

wget http://python.org/ftp/python/2.7.2/Python-2.7.2.tgz
tar -xzvf Python-2.7.2.tgz
cd Python-2.7.2
nice ./configure --prefix=$HOME/local
nice make
nice make install

Ahora tenemos que configurar la shell para que use el nuevo Python 2.7.2 y no la versión desactualizada que estaba instalada.

echo "export PYTHONPATH=\"$HOME/local/lib/python2.7/site-packages\"" >> ~/.bash_profile
echo "export LD_LIBRARY_PATH=\"$HOME/packages/lib\"" >> ~/.bash_profile
echo "export PATH=\"$HOME/local/bin:$PATH\"" >> ~/.bash_profile
logout

Esta es la parte en que tenemos que salir de la sesión y volver a ingresar.

Ahora vamos a comprobar que el nuevo Python quedó instalado. El siguiente comando debería decir algo como /home/<usuario>/local/bin/python

which python

¿Funcionó? Bueno, continuemos.

Nos falta bajar easy_install que luego utilizaremos para actualizar los demás paquetes.

cd ~/installers
wget http://pypi.python.org/packages/2.7/s/setuptools/setuptools-0.6c11-py2.7.egg
sh setuptools-0.6c11-py2.7.egg

Ahora podemos usar el comando easy_install para bajar el resto de utilidades.

easy_install http://pypi.python.org/packages/source/P/Paste/Paste-1.7.5.1.tar.gz
easy_install PIL
easy_install MySQL-python
easy_install Django

La utilidad de Django y MySQL-python debería ser bastante obvia en este punto ¿No?. PIL es un paquete para manipular imágenes (Para crear thumbnails, dibujar y convertir imágenes). Y Paste lo utilizaremos más adelante para depurar problemas en el servidor.

Nos falta configurar Passenger para que utilice nuestro Python. Para esto creamos un archivo passenger_wsgi.py con el siguiente contenido

#!$HOME/local/bin/python
# -*- coding: utf-8 -*-
INTERP = "/home/<usuario>/local/bin/python"
DEBUG = True

import sys
import os
if sys.executable != INTERP: os.execl(INTERP, INTERP, *sys.argv)
sys.path.append(os.getcwd())
os.environ['DJANGO_SETTINGS_MODULE'] = "<proyecto>.settings"
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

if DEBUG:
	from paste.exceptions.errormiddleware import ErrorMiddleware
	application = ErrorMiddleware(application, debug=True)

Recuerden cambiar <usuario> y <proyecto> por el nombre de su usuario y proyecto. Hay una variable DEBUG que permite mostrar en el browser las excepciones que se produzcan. En producción se debería desactivar, pero es muy útil mientras estamos desarrollando. Para esto es que necesitábamos Paste.

Su carpeta $HOME ahora se debería ver así:

installers/
local/
dominio.com/
dominio.com/public/
dominio.com/<proyecto>
dominio.com/passenger_wsgi.py

Con esto ya debería funcionar. Puedes probar en el browser si carga tu aplicación y si hay algún error te debería mostrar la excepción. No olvides desactivar la depuración una vez que pases a producción poniendo DEBUG=False en passenger.py y en settings.py.

FlashPunk para Windows Phone 7

3 ago

Acabo de publicar mi versión de FlashPunk para Windows Phone 7 (XNA). FlashPunk es un engine para hacer juegos 2D tipo Mario o AngryBirds. Fuentes | Más info.

Splash de FlashPunk en el emulador de Windows Phone 7 (XNA)

El screenshot es el emulador de WP7 corriendo el Splash animado de FlashPunk.

Tags: , , ,

Serializar a JSON en Python

3 jun

Un truquillo de esos que ahorran mucho tiempo: Para serializar a JSON sin utilizar librerías externas simplemente es cuestión de crear un diccionario, convertirlo en string y reemplazar comillas simples por comillas dobles:

>>> print unicode({ 'foo': 'hello', 'bar': [4, 8, 15, 16, 23, 42] }).replace("'", '"')
{"foo": "hello", "bar": [4, 8, 15, 16, 23, 42]}

El resultado es un string JSON válido y se puede convertir en un objeto JavaScript con jQuery.parseJSON(str). Este truco maneja perfectamente cadenas unicode o con comillas.

El único problema es al serializar un objeto que pueda contener None (El null de Python) en sus propiedades. En este caso es necesario definir antes el siguiente objeto:

class NullType:
	def __repr__(self):
		return 'null'
null = NullType()

Y al serializar usarlo así:

>>> print unicode({ 'date_of_birth': user.date_of_birth or null }).replace("'", '"')
{"date_of_birth": null}

Funciona muy bien cuando hay que regresar una respuesta rápida por Ajax. Si es algo más complicado les recomiendo usar SimpleJSON que ya viene incluido en Django o para modelos usar la serialización de Django

Tags: , , ,

¿Diferencias en los browsers? ¡Blasfemia!

17 mar

¿Sería mucho pedirle a los señores fabricantes de browsers se pongan de acuerdo al implementar un estándar? Las sombras se ven diferentes en Firefox 4 RC, Chrome 9 e Internet Explorer 9.

Firefox 4, Chrome 9 y IE9 renderizan box-shadow de forma diferente.

Y mientras estamos deseando cosas imposibles… ¿Sería posible que Microsoft implemente el estándar sin BUGS? El IE9 no muestra la sombra si la tabla tiene box-shadow y border-collapse: collapse.

Bueno, será volver al viejo truco de tener una hoja de estilos para todos los browser, una para IE7, otra para IE8 y ahora una más para IE9.

Aquí está el archivo de prueba: box-shadow test

Django Haystack + Xapian en Dreamhost

13 ene

Casi me saco un ojo instalando Xapian en Dreamhost. Lo necesito para usarlo como máquina de búsqueda Haystack en un sitio Django que estoy haciendo (Ya casi les digo cuál, je je).

Haystack es una aplicación Django (un componente) que trae todo listo para hacer un buscador. Solo es cuestión de configurarlo, cambiar unas cuantas plantillas HTML y el buscador queda listo. Y Xapian es la máquina de búsqueda que hace todo el trabajo. Con Haystack tambien se puede usar Solr y Whoosh pero por desempeño me decidí por Xapian.

Me tocó probar muchas versiones y bajar un paquete adicional para poder compilar Xapian. Para documentar el proceso, y por si a alguien le puede servir, aquí están los pasos que tuve que realizar:

NOTA: Yo estoy usando un Python 2.7 que compilé e instalé en el mismo servidor. Todo está en ~/local/ así que si necesitan instalar en otro directorio tendrán que modificar las rutas en los comandos.

Instalar Haystack es fácil. Solo es cuestión de instalar estos dos paquetes:

easy_install http://pypi.python.org/packages/source/d/django-haystack/django-haystack-1.1.0.tar.gz
easy_install http://pypi.python.org/packages/source/x/xapian-haystack/xapian-haystack-1.1.5beta.tar.gz

Esas son las únicas versiones que me funcionaron. Todas las demás combinaciones que probé me dieron errores al crear el índice.

Xapian es más difícil. Primero hay que instalar util-linux-ng y no funciona cualquier versión. La única que no me dio problemas fue la 2.16 (y las probé casi todas):

cd ~/installers
wget ftp://ftp.kernel.org/pub/linux/utils/util-linux/v2.16/util-linux-ng-2.16.tar.gz
tar -xzf util-linux-ng-2.16.tar.gz
cd util-linux-ng-2.16
nice ./configure --prefix=$HOME/local
nice make
nice make install

Ignoren los errores de chgrp que salen al hace el make install. Ahora sí, instalemos xapian-core:

cd ~/installers
wget http://oligarchy.co.uk/xapian/1.2.4/xapian-core-1.2.4.tar.gz
tar -xzf xapian-core-1.2.4.tar.gz
cd xapian-core-1.2.4
nice ./configure --prefix=$HOME/local CPPFLAGS="-I$HOME/local/include" LDFLAGS="-L$HOME/local/lib"
nice make
nice make install

CPPFLAGS y LDFLAGS son necesarios para que encuentre las cabeceras y la biblioteca que instalamos con util-linux-ng. Ahora necesitamos xapian-bindings:

cd ~/installers
wget http://oligarchy.co.uk/xapian/1.2.4/xapian-bindings-1.2.4.tar.gz
tar -xzf xapian-bindings-1.2.4.tar.gz
cd xapian-bindings-1.2.4
nice ./configure --prefix=$HOME/local CPPFLAGS="-I$HOME/local/include" LDFLAGS="-L$HOME/local/lib" XAPIAN_CONFIG=$HOME/local/bin/xapian-config --without-php --without-ruby --without-java --without-csharp --without-tcl --with-python
nice make
nice make install

Listo, ahora solo es cuestión de seguir las instrucciones del tutorial de Haystack.

PD: En Windows es mucho más sencillo porque se pueden bajar los binarios pre-compilados aunque dan uno que otro error. No les recomiendo que lo usen para producción pero para desarrollo funciona. Para instalar todo solo hay que ejecutar estos comandos:

easy_install http://pypi.python.org/packages/source/d/django-haystack/django-haystack-1.1.0.tar.gz
easy_install http://pypi.python.org/packages/source/x/xapian-haystack/xapian-haystack-1.1.5beta.tar.gz

Y luego bajar este instalador y correrlo como administrador:

http://www.flax.co.uk/xapian/124/xapian-python-bindings%20for%20Python%202.7.0%20-1.2.4.win32.exe

Para crear el índice manage.py rebuild_index me da un error todo raro pero manage.py update_index funciona sin problemas.

Tags: , , , ,

Error 0×80049228 usando autenticación de Windows Live ID

5 oct

Horas y horas perdidas luchando con ese error.

Estoy tratando de agregar autenticación por Live ID a un sitio que estoy haciendo. La idea es que quien tenga una cuenta de MSN Messenger/Hotmail pueda entrar al sitio sin registrarse.

Al parecer ya funciona pero de vez en cuando me redirige a esta página:

https://consent.live.com/Error.aspx?mkt=en-US&Detail=0×80049228&ru=…

… con un amable mensaje que dice que hay un problema *con mi sitio*.

Lo poquito que he podido averiguar es que alguna cookie de live.com expira y la única forma es cerrar la sesión en live.com y volver a autenticarse.

Lo peor es que ¡le echan la culpa a mi sitio cuando yo no puedo ni mirar las cookies de live.com!

Será ponerlo en el manual: Si les sale error 0×80049228 cierre la sesión en live.com y vuelva a intentar.

Adicionar días hábiles en python

1 sep

Una función para sumar o restar días hábiles en Python:

from datetime import date, timedelta

(MON, TUE, WED, THU, FRI, SAT, SUN) = range(7)

def addworkdays(start, days, holidays=(), workdays=(MON,TUE,WED,THU,FRI)):
    weeks, days = divmod(days, len(workdays))
    result = start + timedelta(weeks=weeks)
    lo, hi = min(start, result), max(start, result)
    count = len([h for h in holidays if h >= lo and h <= hi])
    days += count * (-1 if days < 0 else 1)
    for _ in range(days):
        result += timedelta(days=1)
        while result in holidays or result.weekday() not in workdays:
            result += timedelta(days=1)
    return result

Ejemplos:

today = date.today()
print 'hoy:', today             # 2010-09-01
print addworkdays(today, 1)     # 2010-09-02
print addworkdays(today, 3)     # 2010-09-06
print addworkdays(today, 20)    # 2010-09-29

print addworkdays(today, -1)    # 2010-08-31
print addworkdays(today, -3)    # 2010-08-27
print addworkdays(today, -20)	# 2010-08-04

Para especificar los días festivos deben pasar un iterable (un tupple o una lista) con las días que serían días hábiles si no fueran declarados festivos. IMPORTANTE: Solo pasen los festivos que caen entre lunes a viernes. No pasen, por ejemplo, el Domingo de Ramos.

Por ejemplo, los siguientes son los días festivos para Colombia sacados de Wikipedia (Solo los que quedan del 2010 y uno que ya pasó para probar):

holidays2010 = (
    date(2010,  8, 16), date(2010, 10, 18),
    date(2010, 11,  1), date(2010, 11, 15),
    date(2010, 12,  8),
)

print addworkdays(today, 100)               # 2011-01-19
print addworkdays(today, 100, holidays2010) # 2011-01-25

print addworkdays(today, -20)               # 2010-08-04
print addworkdays(today, -20, holidays2010) # 2010-08-05

Licencia: MIT

Tags: , , , , ,

Formato de moneda en JavaScript

30 ago

La función quedó un poquito larga pero permite especificar el número de decimales y los separadores (que varían de país a país).

function currency(value, decimals, separators) {
    decimals = decimals >= 0 ? parseInt(decimals, 0) : 2;
    separators = separators || ['.', "'", ','];
    var number = (parseFloat(value) || 0).toFixed(decimals);
    if (number.length <= (4 + decimals))
        return number.replace('.', separators[separators.length - 1]);
    var parts = number.split(/[-.]/);
    value = parts[parts.length > 1 ? parts.length - 2 : 0];
    var result = value.substr(value.length - 3, 3) + (parts.length > 1 ?
        separators[separators.length - 1] + parts[parts.length - 1] : '');
    var start = value.length - 6;
    var idx = 0;
    while (start > -3) {
        result = (start > 0 ? value.substr(start, 3) : value.substr(0, 3 + start))
            + separators[idx] + result;
        idx = (++idx) % 2;
        start -= 3;
    }
    return (parts.length == 3 ? '-' : '') + result;
}

El primer parámetro debe ser un número (cualquier valor inválido regresa “0.00″). Este es el único parámetro obligatorio.

El segundo parámetro es el número de decimales (por defecto 2) y el tercero es un arreglo con los separadores en este orden: Separador de miles, separador de millones, separador de decimales. Por defecto es ['.', "'", ','] que es el que se usa en Colombia.

Algunos ejemplos:

currency(NaN); // "0.00"
currency(0); // "0.00"
currency(123456567.89); // "123'456.567,89"
currency(-123456567.89); // "-123'456.567,89"
currency(1234.56, 1); // "1.234,5"
currency(1234.56, 1, [',', "'", '.']); // "1,234.5"

Licencia: MIT

Tags: , , , ,

Sumar/restar fechas con Javascript

28 may

No encontré un ejemplo que sirviera pero leyendo la documentación me dí cuenta que se puede utilizar el constructor que acepta milisegundos:

var today = new Date();

// Sumar 7 días.
new Date(today.getTime() + (7 * 24 * 3600 * 1000)),

// Restar 5 días
new Date(today.getTime() - (5 * 24 * 3600 * 1000)),

Tenga en cuenta que esta solución ignora los segundos intercalares. En mi caso no son necesarios pero puede que en el suyo si.

Tags:

DataGrid de Silverlight 3 que agrega nuevas filas

1 nov

El DataGrid de Silverlight 3 es una contradicción: Por un lado es mucho más flexible que el de Windows Forms porque es muy fácil agregar cualquier control que queramos a una columna (Solo se agrega el control a la propiedad DataGridTemplateColumn) pero por otro es tan primitivo que no permite que el usuario agregue nuevas filas a la colección.

Para un proyecto que estoy realizando, agregar filas al DataGrid es una funcionalidad básica así que me puse a buscar la solución a este problema. La respuesta que dan por todos lados es agregar un elemento adicional a la colección y eliminarlo si el usuario lo dejó en blanco (Y prepararse para distinguir entre un objeto nuevo y uno modificado).

En mi caso, era una molestia agregar ese elemento en blanco ya que esta funcionalidad se necesitaba en muchos lugares. Así que, en vez de repetir esa lógica por cada DataGrid, decidí buscar la forma de obtener el mismo resultado sin tener que modificar la colección original. La solución a la que llegué me gustó tanto que decidí compartirla en este blog.

Este es el resultado:

Primero un micro-repaso del uso del DataGrid.

Vamos a crear un nuevo proyecto Silverlight con el nombre DataGridAddRow y le decimos que no cree un proyecto Web ya que no lo necesitamos para probar. Abrimos MainPage.xaml y agregamos un DataGrid:

<Grid x:Name="LayoutRoot" Margin="20">
    <data:DataGrid x:Name="m_dgMovies">
    </data:DataGrid>
</Grid>

En MainPage.xaml.cs está el código de la página. En el constructor le pasamos los datos al DataGrid. En este caso es una lista que estoy creando en el método ListOfPixarMovies() pero bien pueden ser datos que salen de un servicio web o algo así.

public partial class MainPage : UserControl
{
	public MainPage()
	{
		InitializeComponent();

		m_dgMovies.ItemsSource = ListOfPixarMovies();
	}

	private List<Movie> ListOfPixarMovies()
	{
		return new List<Movie>
		{
			new Movie { Name = "The Incredibles", Year = 2004 },
			new Movie { Name = "Chicken Little", Year = 2005 },
			new Movie { Name = "Ratatouille", Year = 2007 },
			new Movie { Name = "WALL·E", Year = 2008 },
			new Movie { Name = "Up", Year = 2009 },
		};
	}
}

Y, por supuesto, tenemos que crear la clase Movie. El único requerimiento de esta solución es que la clase debe implementar INotifyPropertyChanged pero si uno está utilizando data binding para editar un objeto normalmente termina implementando INotifyPropertyChanged también. Esta es la clase que utilicé en el ejemplo:

public class Movie
		: System.ComponentModel.INotifyPropertyChanged
{
	string m_name;

	public string Name
	{
		get { return m_name; }
		set
		{
			m_name = value;
			OnPropertyChanged(new PropertyChangedEventArgs("Name"));
		}
	}

	int m_year;

	public int Year
	{
		get { return m_year; }
		set
		{
			m_year = value;
			OnPropertyChanged(new PropertyChangedEventArgs("Year"));
		}
	}

	public event PropertyChangedEventHandler PropertyChanged;

	protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
	{
		if (PropertyChanged != null)
			PropertyChanged(this, e);
	}
}

Nada del otro mundo: Una clase con dos propiedades y cada vez que se modifican esas propiedades ejecutamos el evento PropertyChanged que requiere la interfaz INotifyPropertyChanged.

En este punto podemos ejecutar el proyecto y nos encontramos con un DataGrid con varias filas y podemos editar los datos pero no podemos agregar nuevas filas, como este:

La solución: AppendableCollection

Para permitirle al usuario agregar nuevas filas todo lo que tenemos que hacer es agregar la clase AppendableCollection y en el constructor de la página modificamos la línea en la que pasábamos la lista de películas para envolverla con una instancia de AppendableCollection<Movie>:

public MainPage()
{
	InitializeComponent();

	//m_dgMovies.ItemsSource = ListOfPixarMovies();
	m_dgMovies.ItemsSource = new AppendableCollection<Movie>(ListOfPixarMovies());
}

El resultado es una fila vacía al final de los datos y el usuario ahora puede agregar nuevas filas editando esa fila vacía. Por lo menos hasta que salga una nueva versión del DataGrid con soporte para agregar nuevas filas, esta es probablemente la solución más sencillas para simular esta funcionalidad.

Realmente eso es todo lo que que hay que hacer: Agregar la clase, implementar INotifyPropertyChanged y envolver la colección en AppendableCollection. Ahora puede saltar hasta el final del artículo y descargar el código. Para los tres que se quedaron, así es como funciona:

El DataGrid solo requiere que le pasemos un IEnumerable entonces AppendableCollection necesita implementar esa interfaz. En el método GetEnumerator() lo que hacemos es devolver el Enumerator de la colección original pero al final le agregamos la nueva instancia:

public class AppendableCollection<T>
	: System.Collections.Generic.IEnumerable<T>
	where T : System.ComponentModel.INotifyPropertyChanged, new()
{
	public IEnumerator<T> GetEnumerator()
	{
		return m_collection.Union(m_newItem).GetEnumerator();
	}

	readonly T[] m_newItem;
...
}

Solo nos falta detectar cuando el usuario ha modificado el objeto. Por eso la clase de la colección debe implementar INotifyPropertyChanged. Cuando recibimos el evento de que una propiedad ha sido modificada agregamos la instancia actual a la colección original y creamos una nueva:

void newItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
	m_newItem[0].PropertyChanged -= newItem_PropertyChanged;
	m_collection.Add(m_newItem[0]);
	m_newItem[0] = new T();
	m_newItem[0].PropertyChanged += newItem_PropertyChanged;
}

Solo nos falta avisarle al DataGrid que agregamos un nuevo elemento. Para eso implementamos la interfaz INotifyCollectionChanged y ejecutamos el evento CollectionChanged:

public class AppendableCollection<T>
	: System.Collections.Generic.IEnumerable<T>
	, System.Collections.Specialized.INotifyCollectionChanged
	where T : System.ComponentModel.INotifyPropertyChanged, new()
{
	public event NotifyCollectionChangedEventHandler CollectionChanged;

	protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
	{
		if (CollectionChanged != null)
			CollectionChanged(this, e);
	}

	void newItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
	{
		m_newItem[0].PropertyChanged -= newItem_PropertyChanged;
		m_collection.Add(m_newItem[0]);
		m_newItem[0] = new T();
		m_newItem[0].PropertyChanged += newItem_PropertyChanged;
		OnCollectionChanged(new NotifyCollectionChangedEventArgs(
			NotifyCollectionChangedAction.Add, m_newItem[0],
			m_collection.Count));
	}
...
}

Descargar la clase y el ejemplo

El código de la clase está bajo licencia MIT y puede descargarse desde aquí: DataGridAddRow.zip – Agregar nuevas filas a un DataGrid de Silverlight 3.