Generar archivos rtf en forma dinámica desde Django

En mi anterior artículo les conté sobre pyrtf-ng, una librería para generar archivos rtf en forma fácil desde un programa escrito en Python. Fue la primer alternativa que manejamos a la hora de plantearnos el problema de generar archivos rtf en forma dinámica desde Django.

En este artículo les cuento el aproach que finalmente adoptamos. Si la naturaleza dinámica del documento que queremos generar radica en que ciertas partes del texto tendrán valores dinámicos o ciertas partes pueden estar o no dependiendo de alguna condición, lo que podemos hacer es utilizar un sistema de templates. El formato rtf es un lenguaje de marcas (es texto, no binario!). Por lo que fácilmente podemos usar el subsistema de templates de Django para lograr nuestro objetivo.

Un ejemplo de lo que queremos obtener

Simplificando, si en el sistema la variable nombre vale "Juan" y la variable tratamiento es False, el resultado esperado en el archivo rtf es:

Hola Juan

Si la variable nombre vale "Raúl", la variable tratamiento es "Sr." el resultado esperado en el archivo rtf es:

Hola Sr. Raúl

Para lograrlo necesitamos un archivo hola.rtf (puede tener cualquier nombre y cualquier extensión, pero estos parecen apropiados para el ejemplo) ubicado en algún directorio accesible por el subsistema de templates. Tip: crear un archivo rtf con OpenOffice Writer o WordPad y luego editarlo con un editor de texto para agregar las marcas necesarias para que Django lo procese.

El template

Así se ve el template del ejemplo cuando lo abrimos con un editor de texto, luego de agregar las marcas correspondientes:

{% load filtros %}{\rtf1\ansi\deff0\adeflang1025
{\fonttbl{\f0\froman\fprq2\fcharset0 Bitstream Vera Sans;}{\f1\froman\fprq2\fcharset0 Bitstream Vera Sans;}{\f2\fswiss\fprq2\fcharset0 Bitstream Vera Sans;}{\f3\fnil\fprq2\fcharset0 Bitstream Vera Sans;}}
{\colortbl;\red0\green0\blue0;\red128\green128\blue128;}
[....]
{\rtlch \ltrch\loch\f0\fs24\lang11274\i0\b0 Hola {% if tratamiento %}{{ tratamiento }} {% endif %}{{ nombre|rtf }}}
\par }

El encabezado suele ser largo (esto depende del programa utilizado para crear el documento, con WordPad se obtienen los encabezados más cortos). Las partes importantes están resaltadas en negrita.

El bloque load carga el filtro de nombre rtf que luego es usado. Notar que este bloque debe incluirse en la misma línea que la primer línea del documento o en alguna línea siguiente, pero nunca en la primer línea del archivo y solo. Esto provocará una línea en blanco al principio del documento resultante, un error de sintaxis en el formato rtf, y por lo tanto no podrá ser interpretado por los procesadores de palabras.

Un filtro para codificar caracteres Unicode en rtf

Como rtf no soporta nativamente caracteres Unicode (tildes y otros caracteres no ascii), estos deben ser codificados para ser correctamente interpretados. El siguiente filtro hace esa tarea, es un string filter de Django:

@register.filter(name='rtf')
@stringfilter
def rtf(value):
    if isinstance(value, UnicodeType):
        return "".join(["u%s?" % str(ord(c)) for c in value])
    return value

About Juanjo

Mi nombre es Juanjo Conti, vivo en Santa Fe y soy Ingeniero en Sistemas de Información. Mi lenguaje de programación de cabecera es Python; lo uso para trabajar, estudiar y jugar. Como hobby escribí un libro de cuentos que se puede descargar gratuitamente.
This entry was posted in Django and tagged , . Bookmark the permalink.
  • Gonzalo

    ¡Genial! Marche una aplicación web para traducir el libro de Django al español ;)

  • Juanjo

    No entendí.

  • Alejandro

    No entendi muy bien puedes hacer un ejemplo completo por favor????
    Muchas Gracias

  • Juanjo

    Decime que parte no entendiste y me explico mejor, con un ejemplo concreto y todo si hace falta.

  • Ruperto Leon

    No entiendo nada. He intentado todo pero nada.

  • Pach

    Hola, yo si entiendo pero me parece incompleto, faltaría la view en la que mandas las variables nombre y tratamiento a pyrtf-ng y como lo rediriges luego a una HttpResponse.
    Gracias

  • Juanjo

    La vista es bastante trivial, similar a cualquier vista que pasa variables a un template para renderizarlo. Un ejemplo concreto:

    def regenerar_reserva_rtf(request, reservaid):
        try:
            reserva = ReservaInmueble.objects.get(id=reservaid)
        except:
            return HttpResponseRedirect('/')
        d = {'reserva': reserva}
        out = render_to_string('reserva.rtf', d)
        return HttpResponse(out, mimetype="application/rtf")

    Aquí, se pasa al template el objeto reserva, del cual se obtienen (ya en el template, haciendo por ejemplo reserva.valor) los valores necesarios.
    Notar también el mimetype usando para instanciar el objeto HttpResponse retornado por la vista.

  • Juanjo

    Debo admitir que esto funciona perfecto cuando se accede a la vista siguiendo un link (el cual incluye por ejemplo un valor para reservaid), pero la situación se complica cuando queremos que el documento rtf sea el resultado de apretar un submit button.
    En este caso, por lo general, vamos a esperar del comportamiento de la aplicación que nos dirija a una nueva página (“Su rtf se ha generado” o algo más valioso, pero no el formulario en el que estábamos parados) y una vez que esa página fue renderizada correctamente se nos consulte si queremos abrir o guardar el documento rtf generado.
    Lograr este efecto tiene algunas complicaciones pero puede lograrse con una vista más, guardando algunos valores en la sesión y usando un poco de javascript.
    Por su puesto, este truco de magia queda como para tarea del lector :)

  • Pach

    ahm, vale, así que no utilizas pyrtf-ng para nada, solo si necesitas un documento más complicado.
    buena idea
    Muchas gracias

    PD: En cuanto a lo del formulario no hay problema, eso ya no es algo concreto de generar un rtf
    PD2: Creo que la respuesta es no, hay alguna documentacion, referencia, tutorial de pyrtf-ng a parte de los ejemplos que vienen en el paquete?

  • Juanjo

    Pach, cuando respondí tu comentario anterior tenía en mente mi forma de generar el rtf y no pyrtf-ng. Para pyrtf-ng tendrías que cambiar la vista a algo como:

    def regenerar_reserva_rtf(request, reservaid):
        try:
            reserva = ReservaInmueble.objects.get(id=reservaid)
        except:
            return HttpResponseRedirect('/')
        out = funcion_que_genera_rtf(reserva)
        return HttpResponse(out, mimetype="application/rtf")

    donde funcion_que_genera_rtf usa el módulo pyrtf-ng.

    Si tenés dudas particulares de tu problema, escribime por mail privado y tal vez pueda ayudarte.

  • Pach

    No, está bien, muchas gracias