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. …

Eliminar el fondo de un vídeo

Hoy me ha llegado otro reto que he terminado resolviendo con IA :)

Se trata de un vídeo en el que aparece una persona y detrás una fea y arrugada sábana blanca que en el vídeo se ve amarillenta. El pedido ha sido eliminar el fondo.

Las opciones iniciales eran hacerlo con kdenlive o con blender y muchas horas de marcar puntitos para cada uno de los frames. El vídeo en cuestión tiene un total de 7933 frames y como que no…

He empezado a buscar opciones y he encontrado clipdrop (cerrado aunque tiene opción de API) y he decidido seguir buscando. En la búsqueda he encontrado backgroundremover interesante pero con resultados deplorables y finalmente rembg (con mejores resultados pero enfocado a imágenes).

Así que de nuevo la solución ha sido crear un script. Esta vez me he decantado por bash.

#!/bin/bash

# Fix video's FPS
echo "Converting video to 30 FPS..."
ffmpeg -i video.mp4 -r 30 -c:v libx264 -crf 18 -preset fast -c:a copy video_30.mp4

# Crea temp directories
#mkdir -p original_images
#mkdir -p processed_images

# Generate one image for each frame and save to original_images directory
echo "Extracting frames as images..."
ffmpeg -i video_30.mp4 -vf "fps=30" original_images/frame_%04d.png

# Delete the background of each image of original_images to processed_images
echo "Removing backgrounds from images..."
for img in original_images/frame_*.png; do
    output_name="processed_images/$(basename "$img")"
    rembg i "$img" "$output_name"
done

# Join all the images from processed_images to a video and use audio from video_30.mp4
echo "Combining processed images into a video with 

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 …

Django: definir variables de entorno

Cuando estamos desarrollando una aplicación con django es común que tengamos que ir haciendo cambios en el fichero settings.py, también es común que tengamos unas URL y configuración de la base de datos distinta en cada entorno, sea el desarrollo como el de producción.

En esta entrada veremos como definir variables de entorno que podremos cargar de forma independiente depende de la instancia en la que nos encontremos. Esto también nos permitirá de cara al futuro desplegar una aplicación mediante docker, donde definiremos las variables “desde fuera”.

Vamos a empezar pues.

Lo primero será instalar la librería django-environ (cuidado que está environ, esta no nos sirve)

(venv) laura@melatonina:~/dev/proyecto$ pip3 install django-environ

A continuación en el directorio donde está el fichero settings.py de nuestro proyecto añadiremos un fichero con el siguiente contenido:

(venv) laura@melatonina:~/dev/proyecto$ vi proyecto/.env
DJANGO_SECRET_KEY=************************************************
DJANGO_ALLOWED_HOSTS=localhost,proyecto.capa8.net
DJANGO_DEBUG=True
DATABASE_URL=postgres://proyecto_user:********@localhost:5432/proyecto_db
ENV=development

# Database settings
DB_NAME=proyecto_db
DB_USER=proyecto_user
DB_PASSWORD=********
DB_HOST=localhost

Es importante que no haya ningún espacio ni antes ni después del =, ya que sino fallará.

A continuación en el fichero proyecto/settings.py modificamos las siguientes líneas:

(venv) laura@melatonina:~/dev/proyecto$ vi proyecto/settings.py
import environ, os

env = environ.Env()
environ.Env.read_env()

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env('DJANGO_SECRET_KEY')

# SECURITY WARNING: don't run with debug turned on in production!
# False if not in os.environ because of casting above
DEBUG = env('DJANGO_DEBUG')

ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", [])

# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
DATABASES = {
    'default': env.db('DATABASE_URL'),

Servidor y cliente REST: Django REST framework + requests

Muchos de los posts de éste blog son pequeñas píndolas y recordatorios que me dejo para facilitarme mi tarea diaria de administración de sistemas y últimamente de desarrollo, los comparto públicamente porque al servirme a mi, espero que sirvan a otros. Hoy os traigo un nuevo post de éstos últimos, precisamente de uno que ha sido durante varios años una espinita clavada, programar un servidor API. Hace 3 años hice un módulo de interacción con la API de un proveedor con uno de los programas que tengo en PHP, pero me quedaba lo que era realmente la espinita, la de crear yo el servidor API y permitir que otros programas interactuasen con el mío.

Hace alrededor de 5 años, un cliente que usaba un programa mío me pidió de la posibilidad de interactuar con el programa mediante una API. En aquel entonces traté de desarollarlo, pero con el lenguaje que estaba usando (PHP) y los conocimientos que tenía entonces me resultó tarea imposible, además de la actitud del desarrollador web del cliente. En fin. Así que haber superado éste hito es una inyección de felicidad, superación y autoconfianza.

Vamos a empezar.

Marco y necesidad
Me encuentro con dos aplicaciones que estoy desarrollando con django, voy a llamarlas por su nombre, colibrí y cóndor. Colibrí es un programa experto para hacer auditorías compliance y para cada empresa permite tener un inventario de máquinas. Por otro lado está Cóndor que es un programa de comunicación cliente-empresa. Lo que queremos hacer es que …

Múltiples idiomas en django

No hay momento mas dulce en el desarrollo de un programa el ver que va tomando solidez y que puedas implantar detallitos interesantes en la generación de pdf, navegar por el programa que va a los sitios que tiene que ir, etc. Hoy vamos a hablar de una de estas otras cosas que señalan el avanzado estado de nuestro programa, la internacionalización, el disponer la página en varios idiomas. Vamos a contarlo!

Primero de todo recomiendo tener a mano la documentación de django y éste otro artículo de Alex Dzul al que le he copiado el título y la metedología.

Para resumir, lo que tendremos que hacer será configurar el settings.py, preparar y traducir la interfaz y finalmente generar los ficheros .po. En la documentación te habla primero de la generación de los ficheros de traducción y después desarrollar la traducción en la interfaz (templates), luego contaré porqué es mejor hacerlo al revés.

Así que empezamos

Primero modificaremos el fichero settings.py de nuestro proyecto

(venv) laura@melatonina:~/dev/colibri$ vi colibri/settings.py 
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))

[...]

MIDDLEWARE_CLASSES = (
    # ...
    'django.middleware.locale.LocaleMiddleware',
    # ...
)

[...]

# Internationalization
# https://docs.djangoproject.com/en/2.1/topics/i18n/
# https://www.pythoniza.me/multiples-idiomas-en-django/

LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Europe/Madrid'
USE_I18N = True
USE_L10N = True
USE_TZ = True

from django.utils.translation import ugettext_lazy as _
LANGUAGES = (
    ('en', _('English')),
    ('es', _('Spanish')),
    ('ca', _('Catalan')),
)

# Definimos la ruta de los archivos de idiomas
LOCALE_PATHS = (
    os.path.join(BASE_DIR, 'locale'),
)

# Definimos el procesador de contexto para i18n
#from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS 

Generar pdf con pie/cabecera con python, pdfkit y wkhtmltopdf

Ya llevo varios meses peleándome con pdfkit, al tener que atender otras partes de mi programa lo he ido dejando hasta que realmente he tenido la necesidad de ponerme a hacer funcionar correctamente pdfkit. La documentación que he encontrado por ahí ha sido un poco confusa, además de que estaba teniendo problemas con la versión del wkhtmltopdf y me estaba volviendo loca!

Ahora mismo tengo la necesidad de crear un pdf con sus márgenes, cabecera, pie y numero de página, además no quiero que salga la cabecera y pie de página en la primera página.

Primero de todo tendremos que tener en cuenta que pdfkit hace uso del programa wkhtmltopdf, que está en los repositorios de debian, pero me encuentro que éste no está compilado con qt, como tal al usar según que opciones de la configuración de wkhtmltopdf me soltaba un error similar a éste:

The switch --enable-internal-links, is not support using unpatched qt, and will be ignored.
The switch --footer-center, is not support using unpatched qt, and will be ignored.
The switch --header-html, is not support using unpatched qt, and will be ignored.
The switch --footer-html, is not support using unpatched qt, and will be ignored.

Para ello lo que haremos será primero de todo desinstalar wkhtmlpdf instalado en el sistema e instalar el .deb que nos ofrecen en la página del proyecto de wkhtmltopdf.

# apt remove --purge wkhtmltox
# wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.buster_amd64.deb
# dpkg -i wkhtmltox_0.12.6-1.buster_amd64.deb

UPDATE Para debian 12 bookworm mirar éste repositorio

# wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-3/wkhtmltox_0.12.6.1-3.bookworm_amd64.deb

Instalar entorno de producción de python

Una vez hemos desarrollado una aplicación con django nos interesará ponerla en producción. La forma correcta de hacerlo es usando uwsgi + nginx. Así que vamos a ello.

Mi aplicación corre sobre postgresql así que instalaremos los siguientes paquetes

# apt -y install nginx uwsgi uwsgi-plugin-python3 postgresql python3-psycopg2 libpq-dev git virtualenv memcached

Creamos el usuario y la base de datos postgresql

# su - postgres
$ createuser colibri_user
$ createdb -O colibri_user colibri_db
$ psql colibri_db
colibri_db=# alter user colibri_user with encrypted password 'XXXXXXXX';
colibri_db=# grant all privileges on database colibri_db to colibri_user;

Ahora clonamos el código del programa en /var/www

# cd /var/www/
# git clone git@git.capa8.net:blackhold/colibri.git

Creo el entorno virtual

# cd colibri
# virtualenv -p python3 venv

Entro en el entorno virtual y ejecuto los comandos necesarios para iniciar la aplicación

# source venv/bin/activate
# pip install -r requirements.txt
# ./clean.sh
# python manage.py runserver 0.0.0.0:5001

Ahora configuramos uwsgi para que apunte donde está nuestra aplicación de django

# cd /etc/uwsgi/apps-available
# vi colibri.ini
[uwsgi]
master = true
processes = 10
socket = /tmp/uwsgi-colibri.sock
uid = www-data
gid = www-data

;# with appropriate permissions - *may* be needed
;chmod-socket    = 664

chdir = /var/www/colibri
module = colibri.wsgi
home = /var/www/colibri/venv/
vacuum = true
env = DJANGO_SETTINGS_MODULE=colibri.settings
safe-pidfile = /tmp/uwsgi-colibri.pid
;harakiri = 20 # respawn processes taking more than 20 seconds
;limit-as = 128 # limit the project to 128 MB
max-requests = 5000
daemonize = /var/log/uwsgi/colibri.log
;callable = application
plugin = python37

Guardamos y lo activamos en …

Pyocclient: Apuntes

Abro éste post para ampliar un poco como se usa pyoocclient, una librería de python para gestionar un nextcloud. En éste post voy a mostrar como he solucionado el tema de crear directorios, subir ficheros, compartir ficheros, descargar un fichero (binario) y eliminar ficheros, pero la librería permite incluso hacer gestión de usuarios y algunas cosas mas, realmente da para seguir jugando :) En página podéis ver el código de la librería.…