Compilar wkhtmltopdf para debian bookworm

wkhtmltopdf de momento es la única forma que tengo para generar PDF en python, tras un upgrade de sistema operativo en un servidor tengo la necesidad de actualizar también a la última versión wkhtmltopdf pero al ser un proyecto tan viejo, no han preparado paquetes deb con el qt activado en su repositorio (recuerdo que la versión que viene en los repositorios de debian, no trae qt activado).

No queda otra que compilar los sources y al hacerlo me he encontrado con una gran aventura mañanera (documentación que brilla por su ausencia), así que aquí dejo la solución:

# cd ~
# git clone https://github.com/wkhtmltopdf/wkhtmltopdf.git
# cd wkhtmltopdf
# mkdir build
# cd build

Ahora necesitamos instalar las dependencias necesarias para poder compilar wkhtmltopdf

# apt -y install build-essential xorg libxrender1 libxext6 libfontconfig1 libssl-dev git qtbase5-dev qt5-qmake libqt5webkit5-dev g++ cmake bison flex gperf ruby python3 pkg-config libglu1-mesa-dev libqt5xmlpatterns5-dev libqt5svg5-dev

Y lo compilamos

# qmake ../wkhtmltopdf.pro
# make
# make install

Ahora creo los enlaces simbólicos necesarios para que mi aplicación de django que tiene configurada una ruta específica de donde se encuentran los binarios, los encuentre

# cd /usr/local/bin
# ln -s /usr/bin/wkhtmltopdf .
# ln -s /usr/bin/wkhtmltoimage .

Y listos.

La recomendación del desarrollador es que nos migremos a puppeteer. …

Django: peticiones wsgi y asgi simultaneas (htmx y websockets)

Muy feliz de finalmente tenerlo funcionando! no sé si es la mejor forma, pero la cuestión es que funciona :D

Estos días me estoy peleando en hacer funcionar un chatbot con stream de openai sobre django. Me ha llevado un par de días de hacerlo funcionar pero aquí lo tenemos :D

Básicamente después de perderme entre decenas de páginas de documentación y abrir y cerrar otras decenas más de pestañas del navegador, lo que terminé haciendo fue localizar una aplicación que sí hacía lo que yo quería y luego la he implementado a mi aplicación. La aplicación que funcionaba like a charm en local es Django OpenAI-Powered Chatbot with HTMX Streaming. También recomiendo esta otra documentación (Learn to use Websockets with Django by building your own ChatGPT) que si eres relativamente nuevo con esto de htmx y websockets te va a aclarar mucho el tema :)

Inicié implementando el manual de saaspegasus pero no funcionaba. Así que he decidido directamente integrar el código de django_chatbot a mi aplicación y tampoco funcionaba… hasta que he visto lo de daphne! El python manage.py runserver de toda la vida no es síncrono pero daphne si, y lo que permite es activar las conexiones asíncronas. Al aplicar esto, todo ha empezado a funcionar :D

Después ha venido la parte de ponerlo en producción que de nuevo me he perdido entre múltiples páginas pero finalmente lo que sacado y aquí lo dejo para saber como implementarlo la próxima vez y también para otro …

script para identificar registros duplicados en sqlite3

En uno de mis proyectos estamos ya en la recta final a punto de poner en producción el nuevo programa. A último momento siempre te encuentras con aquellas puñetitas que hacen que la migración no salga como es lo esperado, es por esto que es muy importante practicar la migración al igual que si de una audición se tratase.

La puñetita con la que me he encontrado trata precisamente de la importación de datos mas grande, la de una tabla de 4Gb de datos, que contiene las lecturas de unos sensores, he decidido separarlas en alrededor de 4.000 ficheros de base de datos sqlite3, uno para cada uno de los sensores. Al realizar la importación de cada uno de los sensores de forma individual, de maravilla, pero a la que se hace el proceso de migración de todos los sensores, algunas de las lecturas se duplican y no he conseguido hallar el porqué ocurre.

Ya hace unos días puse un par de controles que si el fichero ya existía o la base de datos estaba siendo usada, pasase al siguiente sensor, aún así seguían duplicandose los datos. La solución era añadir un tercer control que comprobase que el registro ya estaba guardado, pero que ralentizaría la migración de los datos de forma significativa y la primera migración el interés es que sea lo más ágil posible. Así que dándole un par de vueltas y muy importante, tras más de 24h de no tocar el código (se llama descansar), he encontrado …

Gitlab: configurar el envío de correos electrónicos

Gilab es un software para gestionar repositorios de software. Existe github y gitlab tiene también su repositorio público. Gitlab permite la instalación del software en tu propia infraestructura.

En éste otro post explicaba como instalarlo y actualizarlo con sus repositorios. Hoy veremos como configurar gitlab para que mande correos electrónicos.

Lo primero que tendremos que hacer es modificar el fichero de configuración que se encuentra en /etc/gitlab/gitlab.rb, buscar las siguientes líneas, descomentarlas y configurarlas de la siguiente forma (para que funcione con la configuración del servidor de correo electrónico instalado con modoboa)

# vi /etc/gitlab/gitlab.rb
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "mail.capa8.net"
gitlab_rails['smtp_port'] = 587
gitlab_rails['smtp_user_name'] = "noreply@capa8.net"
gitlab_rails['smtp_password'] = "xxxxxxxxxxx"
gitlab_rails['smtp_domain'] = "capa8.net"
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = true
gitlab_rails['smtp_tls'] = false
gitlab_rails['smtp_pool'] = true

gitlab_rails['gitlab_email_enabled'] = true

gitlab_rails['gitlab_email_from'] = 'noreply@capa8.net'
gitlab_rails['gitlab_email_display_name'] = 'GitLab Capa8'
gitlab_rails['gitlab_email_reply_to'] = 'noreply@capa8.net'

A continuación ejecutamos lo siguiente para aplicar la nueva configuración

# gitlab-ctl reconfigure

Una vez realizado el reconfigure, entramos en la consola de gitlab y comprobamos que la configuración se haya aplicado y mandamos un correo electrónico de prueba

# gitlab-rails console -e production
--------------------------------------------------------------------------------
 Ruby:         ruby 3.0.6p216 (2023-03-30 revision 23a532679b) [x86_64-linux]
 GitLab:       15.11.0 (96ee37680b6) FOSS
 GitLab Shell: 14.18.0
 PostgreSQL:   13.8
------------------------------------------------------------[ booted in 32.38s ]
Loading production environment (Rails 6.1.7.2)

irb(main):001:Notify.test_email('info@capa8.net', 'Hello World', 'This is a test message').deliver_now

Y si todo es correcto, deberías recibir un correo electrónico de prueba.…

Consultar procesos con Celery dentro de uwsgi

Después de unos días de trabajo en mi entorno de desarrollo ha llegado el momento de poner la última versión de la aplicación que estoy haciendo a producción. Como el entorno de producción tiene una configuración distinta que la del entorno de desarrollo, aquí han empezado a aparecer los problemas.

Lo primero es que tenemos una cosa ahí enmedio que se llama uwsgi y es quien ejecuta el código, en lugar del python manage.py runserver. Una de las cosas que me he encontrado es que el trozo de código que comprueba que hay tareas ejecutándose en Celery, no funciona correctamente. En mi caso estaba usando

import time, json
_stats = os.popen('celery inspect stats --json').read()
time.sleep(1)
_stats = json.loads(_stats)

No funcionaba correctamente. He tenido que quebrarme un poco la cabeza para inspeccionar que es lo que estaba ocurriendo, por suerte existe una librería que se llama remote_pdb

(venv) root@app-dev:/var/www/html/app# pip3 install remote-pdb

Y añadimos lo siguiente en el trozo de código que queramos inspeccionar que esté corriendo a través de uwsgi

from remote_pdb import RemotePdb
RemotePdb('127.0.0.1', 4444).set_trace()

Y nos connectaremos al trace por telnet

root@digitplan-dev:~# telnet localhost 4444
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
/var/www/html/app/web/rrd.py(465)check_task_status()
-"""
(Pdb) 

A partir de aquí he podido comprobar que algo le pasa a celery, ya que cualquier comando que ejecutase con celery devolvía vacío. No me ha quedado otra que realizar esta comprobación de otra forma

from celery import Celery
_app = Celery("nombreapp")
_running_tasks = _app.control.inspect().active()['celery@' + str(hostname)]
_status 

Django: tareas en background con celery y rabbitmq

Venga, con este post vamos a subir un poco el nivel de nuestras aplicaciones!

Una de las problemáticas que te encuentras al programar con django es que el envío de mails es horrorosamente lento, el proceso de enviar el mail es relativamente rápido, pero no termino de entender el porqué en general es lento (la conexión y sobre todo la desconexión). Para ello una de las soluciones que más o menos ya había aplicado a mi manera con command hacía que la ejecución cada minuto se solapase con la ejecución de minutos anteriores. Hice una ñapa hace unos días hasta encontrar una solución un poco más elegante. Aquí os la traigo! :D

Hay varios posts que hablan de celery, incluso el libro que uso de consulta “Django 3 by Example”, hablan de usar @tank, pero esta opción está descontinuada para la versión 5, así que aquí veremos como hacerlo con Celery 5. Toda la info la he sacado de éste post.

Configurar e instalar celery y rabbitmq en nuestro proyecto/sistema
Lo primero será instalar celery en nuestro proyecto

laura@melatonina:~/dev/whistleblowerbox$ source venv/bin/activate
(venv) laura@melatonina:~/dev/whistleblowerbox$ pip install celery

A continuación instalamos rabbitmq en nuestro sistema. Rabbitmq es el servicio que encolará las tareas que le pasemos y luego las ejecutará en background

root@melatonina:~# apt -y install rabbitmq-server

Ahora en el fichero settings.py de nuestro proyecto añadimos

(venv) laura@melatonina:~/dev/whistleblowerbox$ vi wbox/settings.py
CELERY_BROKER_URL = 'amqp://localhost'

A continuación, en el mismo directorio donde se encuentra el fichero settings.py vamos a crear uno que se …

Cambiar valor de option al seleccionar un campo select con jquery

Pequeño tip para hacer que al seleccionar un valor de un select se cambien las opciones de otro select.

Partimos de los dos select:

<select class="form-control rounded-0" name="sanctioningprocedure" onchange="update_reasons()">
    <option value="">{% trans "Select Sanctioning Procedure" %}</option>
    {% for sc in box.multiple_sanctioning_procedure_group.all %}
        <option value="{{ sc.pk }}">{{ sc|object_name:request.language }}</option>
    {% endfor %}
</select>

<select class="form-control rounded-0" name="complaint_reason">
    <option value="">{% trans "Select Reason" %}</option>
</select>

La parte de javascript:

<script>
function update_reasons(){
    var group = document.getElementById("sanctioningprocedure").value;

    $("#complaint_reason").empty();
    option = '';
    {% for r in all_reasons %}
        if (group == {{ r.group.pk }}) {
            option = option + '<option value="{{ r.pk }}">{{ r|object_name:request.language }}</option>';
        }
     {% endfor %}

     $("#complaint_reason").html(option);
}
</script>

 

 …

Mis andanzas y conocimientos: uno no aprende del día a la mañana

Nunca me he considerado una buena programadora, pero hoy tenía las ganas de avanzar un poco con mi nivel de programación… Realmente, siempre he sido una muy mala programadora, porque no era capaz de entender los conceptos que estaba leyendo o me estaban enseñando. Es simplemente que el enfoque era simplemente incorrecto. Es por ésto que muchas veces me atasco con tonterías…

Aprendí a programar y realmente hacer algo útil con lo aprendido en 2001 (lo de antes con el html en 1998 eran garabatos), cuando tuve la oportunidad de crear un programa para gestionar una imprenta en la que curraba en Cornellà. Además ahí ya estaba empezando a aburrirme, estaba jugando ya con un servidor en gnu/linux, y estaba jugando en hacer pequeños programillas chorras, como descargar los frames del radar de lluvias de la web de metereología y ponerlos en un gif para poderlo visualizar en el applet del tiempo de gnome2. Con ésto pillé curiosidad y sin darme cuenta ya estaba programando y cuando llegó la propuesta (que un poco fue mía porque estaba entusiasmada con lo que estaba haciendo… ¡estaba creando!) pues estaba preparada para ir más allá, con un objetivo, una meta.…

Arreglar el widget del tiempo de mate

Los usuarios del sistema de escritorio mate os habréis encontrado que ya hace unos días que el widget del tiempo no devuelve datos, el motivo de ello es que la página web de dónde se recolectaban los datos (weather.noaa.gov) ha dejado de funcionar. Para ello la solución es modificar la librería.

El código que hay en github puede requerir versiones de dependencias mas nuevas que las que hay en nuestro sistema, así que una de las soluciones es descargarnos el código de la versión que tenemos instalada en nuestro sistema.
Para no guarrear mucho mi sistema voy a descargarme el código en una maquina virtual (con la misma versión de sistema operativo que en mi portátil), instalar las dependencias necesarias para compilar la librería, descargar el código y modificar la librería, compilar la librería y finalmente copiar el fichero resultante a mi sistema.…

Gif animado lluvias meteocat.com

Pues harta ya de cada vez que llueve tener que ir a la página de meteocat.com a mirar simplemente lo que me interesa, hago que una maquina lo haga para mi y me genere un gif animado para ponerlo en el desklet del escritorio.

Para hacerlo funcionar simplemente necesitamos php5-gd:

# apt-get install php5-gd

el código

# Generador d'imatge animada del radar metereològic meteocat.com
# Desenvolupat per Blackhold
# 2011-11-16

# I get the json file
$file= file_get_contents("http://www.meteo.cat/servmet/radar/images/cappicor_catalunya_10dBZ/images.json");

# And process the data
#var_dump(json_decode($file));
$parsed=json_decode($file);
$array=$parsed->{'items'};

//echo $array[0]->{'src'};

$length=count($array);

for ($i=0;$i{'src'},"./"));

        imagegif($png, "./meteo.".$i.".gif");

        imagedestroy($png);
}

exec("/usr/bin/convert -delay 60 -loop 0 ./meteo.*.gif ./meteo.gif");
exec("rm ./meteo.*.gif");

Y para que se vaya actualizando de vez en cuando lo añadimos al cron que lo vaya verificando cada 6 minutos, periodo con el que se actualiza el json de meteocat.

# vi /etc/crontab
# generador meteocat
*/6 * * * * root php /var/www/meteocat.php

Aquí os dejo con un ejemplo (no abusar! el código es muy simple, lo puedes poner en local o en un servidor tocho -compartid los links!-).