//barrabarra

JAVIER ROMERO
Programación web y escritorio
 

Our blog, keeping you up-to-date on our latest news.

 

De celery beat, multiples workers/colas y tareas programadas

noviembre 21, 2013 at 1:48 pm | Django | No comment

 

Desde hace algunos días vengo investigando por qué unas tareas programadas (periodic task) en django-celery no se estaban lanzando, aunque al comprobar el log de celery beat quedaba claro que se estaban recuperando correctamente desde las tablas de tareas programadas.

Se da la situación de que en nuestro sistema tenemos un par de workers distintos para controlar la concurrencia con el tamaño de las colas.
El worker por defecto tiene una cola de 8 elementos y tenemos otro de 1.
Resumiendo mucho, al declarar la tarea el codigo es tal que así:


@task(queue="socialmessages_1slot")
def task_send_pending_messages():
...

y para que la tarea periódica se enrute bien hacia el worker secundario con un único slot que maneja la cola “socialmessage_1slot”, hay que incluir el nombre de la cola al crear la tarea periódica en la administración de Django (en el bloque Execution options).

Con esto celery beat enruta correctamente la tarea al worker que le corresponde.
Me parece curioso este comportamiento porque no ocurre lo mismo con otras tareas que tienen igualmente declarada su cola que es manejada por el worker por defecto. Igual es un error porque esto que apunto ocurre con la version 3.0.23 y no con la más actual :-/

 

Error al indexar documentos en Elastic Search: MapperParsingException

julio 9, 2013 at 7:30 pm | Blog, Django, ElasticSearch, Programación | No comment

 

Hace unos minutos que he terminado de pelearme con ElasticSearch en el proyecto en el que trabajo así que vamos a dejar por aquí un apunte para que en el futuro pueda servir de referencia.

La situación es tener una instancia de un la clase JobOffer, la cual serializo, trato de antemano y envío a ES con el uso de la genial biblioteca de Python requests. El problema llega cuando el documento no se indexa y en su lugar recibo el siguiente error:

{"error":"MapperParsingException[object mapping for [joboffer] tried to parse as object, but got EOF, has a concrete value been provided to it?]","status":400}

Cuando esto -o algo similar a esto- ocurra tendrás que detenerte y examinar lado a lado la estructura en JSON que estás enviándole al índice y el mapping de dicho índice.

Cuando lo hagas, seguramente te encontrarás con que en alguna de las propiedades definidas en el mapping tiene algún atributo y que tu JSON no tiene esa atributo en esa propiedad, se ve más claro en el siguiente ejemplo, en el que primero muestro el mapping de la propiedad language (que es la que me estaba dando problemas) y a continuación el contenido del diccionario que estaba enviando:

language: {
    dynamic: "true",
    properties: {
        name: {
            type: "string"
        },
        pk: {
            type: "long",
            ignore_malformed: false
        },
    }
},

print data_dict["language"]
>> "es_ES"

Como se aprecia, en la propiedad language el mapeo esperaba recibir dos campos, pk y name, pero recibía directamente un valor “es_ES” y de ahí el error.

 

Configurar Ubuntu 12.10 en Linode para servir aplicaciones de Django

abril 11, 2013 at 12:14 pm | Blog, Django, Programación, virtualenv | No comment

 

Hemos creado una nueva máquina virtual usando Linode y vamos a ver cómo configurar un entorno virtual y servir webs con django.

Lo primero que haremos será loguearnos mediante ssh a nuestra cuenta, una vez echo esto, ponemos al día los paquetes:

apt-get update && apt-get upgrade

A continuación instalamos los programas que vamos a necesitar:

apt-get install git python2.7 virtualenvwrapper

Aceptamos las dependencias y los dejamos instalados. Como puede que hay descargado nuevas versiones de la imagen del kernel reiniciamos y de paso aprovechamos ese reinicio para que virtualenvwrapper añada algunas líneas de configuración a nuestro archivo de configuración del intérprete de comandos. Esperamos unos segundos y volvemos a conectar vía ssh.

Cuando ya hemos conectado de nuevo vamos a crear el proyecto y su entorno virtual:

mkvirtualenv -p /usr/bin/python2.7 mi-proyecto-django

(usamos la opción -p para indicar el ejecutable de Python 2, ya que la última versión de Ubuntu llega de fábrica con Python 3.
Cuando hemos creado el entorno automáticamente estaremos dentro, como se puede observar porque el prompt de la terminal ha cambiado a (mi-proyecto-django)usuario@localhost:…

Nos vamos a /var/www/ (que será el directorio en el que contengamos nuestros proyectos de django) y clonamos desde nuestro repositorio/subimos via ftp/similar el código. Lo recomendable es usar un sistema de control de versiones, github o bitbucket ofrecen ambas servicio gratuito de alojamiento. Cuando lo tenemos descargado podemos seguir con la configuración del proyecto. Al usar entornos virtuales es muy sencillo replicar las condiciones de las máquinas de desarrollo en el entorno de producción con la ayuda del programa pip. Mientras vamos desarrollando e instalando programas y dependencias en el entorno virtual no tenemos que ir apuntando cada programa y cada versión, podemos obtenerlos todos de una vez ejecutando

pip freeze

pip freeze > requirements.txt es una forma de volcar la salida de los resultados de paquetes y versiones a un único archivo de requisitos, que debería ir acompañando al código fuente en el control de versiones para que al subirlo al hosting de producción estuviera preparado para instalarlas.

Contando con que tenemos ese archivo con los nombres y versiones de paquetes, podemos instalarlos fácilmente con la ejecución en el hosting de:

pip install -r requirements.txt

Vigila los mensajes que devuelve la ejecución ya que por ejemplo en una de mis dependencias (django-chronograph) la versión de “distribute” es demasiado antigua y tengo que actualizarla antes de poder completar la intalación de todos los requisitos. El propio mensaje te informa de que eso se resuelve ejecutando

easy_install -U distribute

Como ya le hemos indicado al virtualenv que la versión de python a usar es la 2.7 no hace falta que indiquemos que queremos usar “easy_install 2.7 -U distribute” ya que son equivalentes. Cuando hemos actualizado distribute volvemos a instalar los requisitos de requirements.txt, terminará de descargar e instalar los paquetes que quedaron pendientes y tendremos el entorno correctamente preparado. Si vas a instalar PIL date una vuelta por esta entrada en la que comentan cómo habilitar el soporte para varios formatos: http://askubuntu.com/questions/156484/how-do-i-install-python-imaging-library-pil

Vamos con el resto de programas que vamos a utilizar: necesitaremos un servidor de bases de datos, un servidor web y para facilitarnos la vida con la gestión de la base de datos una interfaz gráfica, para ello instalamos los paquetes:

apt-get install mysql-server phpmyadmin nginx php5-fpm

y sus dependencias.

Vamos con la configuración del software. Lo primero que te preguntará el sistema al instalar el servidor MySQL es que le proporciones una clave para el usuario root, hazlo y termina de configurarlo. Tras eso, el asistente de instalación de phpmyadmin te indicará que deben instalarse ciertas tablas de configuración, acepta, indícale la clave de root que anteriormente habías elegido y por último deja que se elija una clave aleatoria para el acceso de phpmyadmin a la base de datos.

En el siguiente paso vamos a comprobar si el servidor NGINX está funcionando, primero nos aseguramos de que se esté ejecutando con la orden:

service nginx start

y posteriormente vamos a la ip de nuestro hosting, en la que deberíamos ver un mensaje similar a: Welcome to nginx!
Si lo vemos, continuaremos creando la configuración para servir phpmyadmin. Crearemos un archivo phpmyadmin en la ruta /etc/nginx/sites-available/ con el contenido siguiente:


server {
listen 81; ## listen for ipv4; this line is default and implied
#listen [::]:80 default_server ipv6only=on; ## listen for ipv6

root /usr/share/;
index index.php index.html index.htm;

# Make site accessible from http://localhost/
server_name localhost;

# Make sure files with the following extensions do not get loaded by nginx because nginx would display the source code, and these files can contain PASSWORDS!
location ~* \.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)$|^(\..*|Entries.*|Repository|Root|Tag|Template)$|\.php_ {
deny all;
}

# Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac).
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}

location ~* \.(jpg|jpeg|png|gif|css|js|ico)$ {
expires max;
log_not_found off;
}

location ~ \.php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
location /phpmyadmin {
root /usr/share/;
index index.php index.html index.htm;
location ~ ^/phpmyadmin/(.+\.php)$ {
try_files $uri =404;
root /usr/share/;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# With php5-fpm:
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
}
location ~* ^/phpmyadmin/(.+\.(jpg|jpeg|gif|css|png|js|ico|html|xml|txt))$ {
root /usr/share/;
}
}

location /phpMyAdmin {
rewrite ^/* /phpmyadmin last;
}
}

Con esto tendremos a NGINX sirviendo phpyadmin a través de php-fpm, y vamos a intentar acceder a él para ver si lo hemos configurado correctamente.
Primero arrancamos php5-fpm por si no lo estuviera:

service php5-fpm start

Habilitamos la configuración que hemos creado

ln -s /etc/nginx/sites-available/phpmyadmin /etc/nginx/sites-enabled/

y reiniciamos NGINX para que lea la nueva configuración:

service nginx restart

Por último vamos a probar si está sirviendo en el puerto que hemos configurado (el 81) introduciendo el dominio/ip seguido de :81, algo como 111.222.333.444:81 (esta ip es de pega)
Nos debería dar la bienvenida la página de login de phpmyadmin.

Ahora, si tenéis algunas tablas de la base de datos que queráis importar podéis hacerlo mediante la terminal con

mysql -u username -p database_name < dumpfile.sql

Comprobamos antes de continuar que todo está correctamente ejecutando

./manage.py validate

Y pasamos a configurar el sitio que servirá NGINX en /etc/nginx/sites-available/proyecto y luego hacemos un soft link a él en /sites-enabled/


server {
index index.html index.htm index.php;
#client_max_body_size i8M;

# Make site accessible from http://localhost/
listen 82;
#server_name tu.dominio.com;

location /media/ { # MEDIA_URL
root /var/www/proyecto/ruta/a/carpeta/padre/de/media; # MEDIA_ROOT
autoindex off;
access_log off;
expires 30d;
}

location /static/ { # STATIC_URL
root /var/www/proyecto/ruta/a/carpeta/padre/de/static; # STATIC_ROOT
autoindex off;
access_log off;
expires 30d;
}

location /favicon.ico {
root /var/www/proyecto/ruta/a/carpeta/padre/de/favicon.ico;
try_files $uri $uri/ /static/img/favicon.png;
autoindex off;
access_log off;
expires 30d;
}

# Serve shakingjobs django project at /var/www
location / {
root /var/www/proyecto/ruta/a/manage.py; # Ej /var/www/proyecto/miproyecto , no incluir manage.py
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_connect_timeout 60;
proxy_read_timeout 60;
proxy_pass http://localhost:8000/; # puerto local en el que se lanzará el servidor gunicorn
}
}

Probaremos a lanzar el servidor gunicorn y a reiniciar el servidor NGINX con:


./manage.py run_gunicorn &
service nginx restart

Acudiendo a tu.direccion.ip:82 deberás encontrar tu proyecto ejecutándose. Si va bien podemos adornar un poquito el lanzamiento de gunicorn, controlar logs y similar con un script un poco más detallado como un runserver.sh:


#!/bin/bash
set -e
LOGFILE=/var/log/gunicorn/proyecto.log
LOGDIR=$(dirname $LOGFILE)
NUM_WORKERS=3
cd /var/www/ruta/a/proyecto/
source /home/django/.virtualenvs/proyecto/bin/activate
test -d $LOGDIR || mkdir -p $LOGDIR
exec gunicorn_django -w $NUM_WORKERS \
--log-level=debug --bind=127.0.0.1:8002 \
--log-file=$LOGFILE 2>>$LOGFILE

Bien, sólo queda automatizar el lanzamiento de gunicorn en cada inicio del sistema. Esto se hace creando un archivo de configuración en /etc/init/proyecto.conf con un contenido similar a:


description "My Project gunicorn instance"
start on runlevel [2345]
stop on runlevel [06]
respawn
respawn limit 10 5
exec /var/www/proyecto/runserver.sh

Y tras eso habilitarlo como servicio con la orden:

ln -s /lib/init/upstart-job /etc/init.d/proyecto

Con eso podremos arrancar el servicio con service proyecto start y detenerlo con service proyecto stop, además de que se iniciará en los niveles 2, 3, 4 y 5 del sistema

 

Django no traduce algunos formularios/modelos/cadenas

noviembre 3, 2011 at 11:49 am | Blog, Django, Programación | No comment

 

Esta es cortita.

Django incorpora unas grandes herramientas para traducir los sitios a diversos idiomas, pero hay un pequeño pero: todo debe estar en el mismo idioma base y luego incorporar las traducciones necesarias.

Esto lo he descubierto después de estar unas pocas de horas rebuscando por qué había algunos formularios y cadenas que no se mostraban correctamente en inglés y sólo aparecían en español.
La clave estaba en que los formularios eran parte de una aplicación antigua que había incluido en  el proyecto (ya sabes, don’t repeat yourself) y estaban integramente en español. Así que he tenido que pasarlo a inglés y crear el archivo de traducciones a español como para el resto de aplicaciones.

 

Generar distintos CSSs para versiones de escritorio y móvil con SASS

septiembre 28, 2011 at 7:42 pm | Blog, Django, Sass | No comment

 

En los últimos trabajos que he realizado he utilizado “diseños sensibles” (responsive design en inglés) para que las webs se adaptaran a los dispositivos en los que iban a ser vistos.

Así, ver la web en una pantalla de gran tamaño como puede ser la de un ordenador o un televisor mostraba la versión normal extendida, mientras que había otras versiones con imágenes de menor tamaño o con el contenido en una única columna en lugar de dividirlo en 2 para netbooks y por último versiones especialmente reducidas para facilitar la navegación en smartphones y no utilizar muchos datos.

Pero me quedaba la espinita de que realmente no le daba la opción al usuario de ver la versión “completa” si así lo quería, y no quería seguir así, de modo que en el proyecto que tengo entre manos actualmente he utilizado dos herramientas estupendas para esto:

De Sass ya hablé un poco en una entrada anterior, pero gracias a @TheSassWay sigo alucinando con lo potente que es. La base de Sass es que permite crear ficheros con reglas a partir de otros ficheros más pequeños a los que se llama partials.

Un fichero full.scss puede ser así:

@import "partials/more";
@import "partials/global";
@import "partials/default";
@import "partials/tablet";
@import "partials/mobile";
@import "partials/wide-mobile";
@import "partials/high-px-ratio";

Y otro fichero desktop.scss ser así:

@import "partials/more";
@import "partials/global";
@import "partials/default";

Los archivos _more.scss, _global.scss y _default.scss serán los mismos, y cuando se les pase el intérprete Compass, se generarán dos archivos distintos full.css y desktop.css con tan sólo los contenidos de los parciales indicados. Sólo se compilan los archivos *.scss que no tienen un _ como inicio del nombre, y pueden tenerse tantos archivos *.scss como se necesiten. ¿No es una maravilla? No había pensado en que se podían crear varios hasta que no me lo indicó @TheSassWay :-D

¿Qué se consigue con esto? Evidentemente, tienes un css “sensible” y uno no sensible para la versión completa. Por defecto mostramos el “sensible”, pero si no piden expresamente la versión completa de la web, podremos servirla sin tener que andarnos copiando el css generado y eliminando líneas (que era lo que hacía hasta el consejo de @TheSassWay U_U)

Ahora sólo queda servir el css apropiado. Para eso utilizaremos Django-mobile. Django-mobile entiende de “sabores” y por defecto interpreta las peticiones mediante un middleware y las clasifica en “full” o “mobile”. Yo lo he modificado para incluir también un sabor “tablet”.

Mirando un poco la documentación, nos encontramos con que en nuestras plantillas podemos utilizar la variable {{ flavour }} para identificar qué “sabor” se está sirviendo. Y además podemos elegir el sabor manualmente añadiendo un parámetro GET a la url del tipo ?flavour=full

Con lo que en nuestra plantilla podemos añadir algo del tipo:

{% if flavour != 'full' %}
    <a href="?flavour=full">Ver versión estándar</a>
{% else %}
    <a href="?flavour=mobile">Ver versión móvil</a> 
{% endif %}

para cambiar el “sabor” actual y variar el CSS que se sirve con:

{% if flavour == "full" %}
    <link rel="stylesheet" type='text/css' href="{{ STATIC_URL }}css/desktop.css?v=2">
{% else %}
    <link rel="stylesheet" type='text/css' href="{{ STATIC_URL }}css/full.css?v=2">
{% endif %}

en la sección <head> de nuestra plantilla base.

Además de para esto, también aprovecho django-mobile para generar imágenes de distinto tamaño gracias a sorl-thumbnail según vayan a servirse a un dispositivo u a otro, o para ocultar completamente alguna sección.

Por ejemplo, en un listado de productos no tiene sentido para un móvil que se conecta a la web vía 3G descargar fotos de 1024×682 pixels, cuando para el diseño sensible que se ha utilizado basta con una imagen 240×160: consumo de datos que se ahorra el visitante y página que carga más rápido con algo como:

{% load thumbnail %}

<div id="work_list">
    {% for work in object_list %}
            <div class="work">
            <a href="{{ work.get_absolute_url }}">
                {% if flavour == "mobile" %}
                    {% thumbnail work.main_image "240x160" crop="center" as img %}
                    <img src="{{ img.url }}" alt="{{ work.name }}" height="{{ img.height }}" width="{{ img.width }}" />
                    {% endthumbnail %}
                {% else %}{% if flavour == "tablet" %}
                    {% thumbnail work.main_image "640x426" crop="center" as img %}
                    <img src="{{ img.url }}" alt="{{ work.name }}" height="{{ img.height }}" width="{{ img.width }}" />
                    {% endthumbnail %}
                {% else %}
                    {% thumbnail work.main_image "1024x682" crop="center" as img %}
                    <img src="{{ img.url }}" alt="{{ work.name }}" height="{{ img.height }}" width="{{ img.width }}" />
                    {% endthumbnail %}
                {% endif %}{% endif %}   
            </a>
            <a class="product" href="{{ work.product.get_absolute_url }}">{{ work.product }}</a>
            </div>
    {% endfor %}
</div>
 

Categorias:

Archivo: