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 …

Django runserver sobre https

Estos dos últimos meses he estado trabajando con una tienda virtual desarrollada con django llamada django oscar. Muy interesante el juguete, tunearla ha habido momentos de todo, en algunos momentos era una delicia por la documentación que tienen y por otros iba más perdida que un pingüino en el desierto y los atascos eran monumentales además de la presión del cliente que tenía que tener el proyecto en funcionamiento un mes y pico antes de que se me diese acceso al código para poder arreglar y aplicar las funcionalidades extra que se requerían. El proyecto era uno que había dejado a medias otro desarrollador y tuve que rehacer todo el código porqué tal como se me entregó la aplicación había cosas básicas de funcionamiento rotas, además de variables en inglés, en castellano y catalán (no es el primer proyecto que me encuentro así).

Django oscar se usa como aplicación de Backend y de frontend hay desplegado un NextJS que tras dar alguna que otra vuelta he terminado modificando alguna cosa.

En entorno local de desarrollo todo funcionaba correctamente, pero al ponerlo en producción la cosa se ha puesto divertida y ha requerido investigar varias cosas que voy a exponer en este post en modo de notas.

La aplicación de frontend se conecta con django oscar por API y hasta que no añadí la opción CSRF_TRUSTED_ORIGINS la comunicación era infructuosa.

# vi settings.py
# this allows nextjs to connect to django without using csrf_token
CSRF_TRUSTED_ORIGINS = 'http://127.0.0.1'
CSRF_COOKIE_SAMESITE = "None"

Django 4 y zonas horarias (timezone)

Aquí estamos con mi primer proyecto en el que tengo que tener en cuenta la zona horaria del usuario que visualiza los datos de la aplicación que estoy creando.

La aplicación recolecta unos datos de unos sensores y debe almacenarlos en la base de datos, pero luego estos datos que almaceno en mi servidor, tienen que corresponder a la hora del país donde se encuentra el sensor. Con esto empiezas a preguntarte, ¿cómo debo almacenar el dato? ¿cómo lo muestro?

Respuesta rápida:
– Pones tu aplicación en UTC
– Almacenas todos los datos de los sensores con UTC
– Visualizas los datos según la configuración horaria del navegador y/o del usuario

Al investigar sobre el tema, me han pasado un post en un blog muy divertido en el que explica todo esto de las zonas horarias, ¿sabías que existen 244 zonas horarias distintas y un total de 195 países? Y no todas las zonas horarias saltan de hora a hora, además de que nos encontramos en un país que tiene el horario de invierno y el de verano, cosa que no tienen todos los países. Vamos, todo esto de las zonas horarias es un cacao guapo, guapo, guapo.

Programando con Django, una cosa que me he encontrado varias veces es que hay 2 librerías que se llaman igual y se usan distinto (import datetime vs from datetime import datetime), un auténtico quebradero de cabeza que hace que código que funciona en una parte, no lo haga en otra. Al …

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 …