Generar ficheros Word con Django y python-docx

Os explicaré como poder generar ficheros Word en formato .docx de forma dinámica con Django. En mi caso lo he utilizado para desarrollar una funcionalidad para StoryDevil, que permite a los usuarios exportar en .docx una serie de documentos necesarios para enviar cartas de presentación y propuestas editoriales en base a unos datos generados en la aplicación.

De esta forma, el usuario tiene ficheros editables que puede personalizar antes de generar un PDF o hacer el envío definitivo. Y esto, con Django y el paquete python-docx, es sorprendentemente sencillo.

Además, os compartiré el código que utilicé para que el documento se descargue directamente en el navegador del usuario, en lugar de almacenarse en el servidor.

image 5
Interfaz de StoryDevil con la funcionalidad que genera los .docx

Con la librería python-docx (Github y PyPI) puedes crear documentos Word y, como decía, de forma bastante sencilla. Tiene soporte para párrafos, estilos, tablas, personalizar cabecera y pié, objetos de forma, secciones, y más. Es compatible con Python 2.6, 2.7, 3.3 y 3.4. La única dependencia adicional es la librería lxml.

image 4

A modo de ejemplo, estas tres líneas son suficientes para crear un documento, añadirle un título, y un párrafo a continuación.

document = Document()
document.add_heading('Título del documento', 0)
p = document.add_paragraph('Esto es un documento Word generado con Python.')

Instalación

Como la mayoría de paquetes Python, la instalación se puede hacer con la utilidad pip o también con easy_install. Si preferimos el método manual, podemos descargar la última versión, descomprimirla, y instalar con python setup.py install.

# Instalar con pip
pip install python-docx

# Instalar con easy_install
easy_install python-docx

#Instalación manual
tar xvzf python-docx-{version}.tar.gz
cd python-docx-{version}
python setup.py install

También puedes añadir el paquete en el fichero requirements.txt de Django especificando la última versión compatible.

Django==3.0.3
...
python-docx==0.8.10

Crear el formulario para la descarga

Para este ejemplo vamos a crear un formulario sencillo con un botón que, tras pulsarlo, llamará a una vista que generará el documento y lo descargará directamente en el navegador.

Este es el formulario con un action que llamará a la URL que apunta a la vista.

<form action="{% url 'publication:export' %}" method="post" role="form">
    {% csrf_token %}
    <button type="submit" class="btn btn-success">
        <i class="fa fa-download"></i> Generar y descargar .docx
    </button>
</form>

En el fichero urls.py configuramos dicha URL con:

app_name = 'publication'
urlpatterns = [
    path('', views.publication, name='publication'),
    path('coverletter/export', views.coverletter_export, name='coverletter_export'),
    ....
]

De esta forma podemos llamar a la función url en la plantilla de Django para que cree automáticamente la URL correcta que llamará a nuestra vista.

Configurar la vista que generará el documento y la descarga

En el fichero views.py, he creado una función llamada coverletter_export tal y como se indica en el fichero urls.py.

Antes que nada, hay que importar lo necesario:

# Para la generación y descarga del fichero
import io

# Para utilizar algunas de las funciones de la librería
from docx import Document
from docx.shared import Inches, Pt
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.text import WD_BREAK

En mi caso he importado algunas funciones concretas de la API de python-docx como Inches y Pt, que permiten indicar longitudes (de párrafos por ejemplo) en pulgadas y en puntos, además de WD_ALIGN_PARAGRAPH que puedes utilizar para personalizar la alineación de los párrafos, pero debes consultar la documentación para ver qué necesitas exactamente.

Al grano, aquí va el ejemplo entero para generar un documento Word básico con un título centrado, y dos párrafos de texto.

Algunos detalles:

  • Podemos crear párrafos en blanco con add_paragraph()
  • Con add_heading() añadimos texto en un elemento de título, y con alignment = WD_ALIGN_PARAGRAPH.CENTER lo centramos.
  • Es posible que si le pasamos un texto con líneas en blanco a la función add_paragraph(), nos lo renderizará con párrafos enteros en blanco. Podemos recorrer el texto con splitlines() descartando las líneas en blanco y se renderizará como es debido.
  • También podemos modificar el espaciado entre líneas usando .paragraph_format.line_spacing e indicándole un valor float.
  • Podemos usar ‘\n’ en el texto para añadir saltos de línea.
def coverletter_export(request):
    document = Document()
 
    # Get the user's fullname
    if request.user.get_full_name():
        document_data_full_name = request.user.get_full_name()
    else:
        document_data_full_name = "[NOMBRE] [APELLIDOS]"
 
    # Print the user's name
    document_elements_heading = document.add_heading(document_data_full_name, 0)
    document_elements_heading.alignment = WD_ALIGN_PARAGRAPH.CENTER
 
    # Add empty paragraph
    document.add_paragraph()
 
    # Print biography and careerpath
    for line in document_data_biography.splitlines():
        if line:
            document_element_biography = document.add_paragraph(line)   
            document_element_biography.alignment = WD_ALIGN_PARAGRAPH.LEFT
            document_element_biography.paragraph_format.line_spacing = 1.15
 
    for line in document_data_careerpath.splitlines():
        if line:
            document_element_careerpath = document.add_paragraph(line)   
            document_element_careerpath.alignment = WD_ALIGN_PARAGRAPH.LEFT
            document_element_careerpath.paragraph_format.line_spacing = 1.15
 
    # Add empty paragraph
    document.add_paragraph()
 
    # Sincerely and name
    document.add_paragraph("Atentamente,\n" + document_data_full_name)

Descarga del documento generado

Como parte final, queda la generación del fichero docx.

En el ejemplo oficial se utiliza la función save() que guarda el fichero en el servidor.

document.save('demo.docx')

Pero en mi caso programé la siguiente función para que en lugar de guardarse en el servidor, el fichero se descargue directamente en el navegador del usuario y no se guarde ninguna copia en nuestros sistemas.

Basta con hacer lo siguiente:

# Save document to memory and download to the user's browser
document_data = io.BytesIO()
document.save(document_data)
document_data.seek(0)
response = HttpResponse(
    document_data.getvalue(),
    content_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
)
response["Content-Disposition"] = 'attachment; filename = "Carta de Presentación.docx"'
response["Content-Encoding"] = "UTF-8"
return response

Con esto deberías tener todo lo necesario para generar tus propios ficheros Word desde cualquier aplicación Python.