Введение
.docx открытый формат документов от корпорации зла, в ответ на формат .odf.
Если решили использовать .docx, то нужно знать, что это на самом деле .zip архив. В котором содержится вся инфомация о документе - текст, изображения, стили, оформление. Сам текст содержится в xml (файл в архиве /word/document.xml)
Для Django существует несколько различных способов создать документ в формате .docx. Во-первых python-docx позволяет создать документ, используя дружественный интерфейс для работы с объектом (только через этот интерфейс, мы не можем работать с xml). Во-вторых есть библиотека docxtpl, которая зависит от python-docx и шаблонизатора jinja2. Мы можем использовать синтаксис шаблонизатора непосредственно в документе. В-третьих вариант распаковать zip архив и изменять xml при помощи библиотеки lxml. Поддержка zip встроена в python.
Все эти способы имеют преимущества и недостатки и каждый решит сам для себя что использовать.
Мой алгоритм
Для себя я выбрал следующий алгоритм создания отчетов в формате .docx. Я руководствовался соотношением необходимых-лишних библиотек в проекте, простотой работы, использования всех возможностей шаблонизатора, таких как собственные тэги, наследование.
- Создаем документ
document.docx(содержащий все стили, и текст). Извлекаем из негоdocument.xml. - Из
document.xmlсоздаем шаблон, используя теги django шаблонизатора - Рендерим шаблон
document.xmlнаполняя его контекстом, получаем xml текст - Парсим этот текст с помощью lxml получаем блок документа
- Открываем документ
document.docxиспользуяpython-docx - Подменяем body документа на наш блок
- Сохраняем документ с помощью
python-docxс именемtest.docx
Листинг программы open_docx_test.py
# encoding: utf-8
from django.template import Template, Context
from docx import Document
from lxml import etree
from django.conf import settings
settings.configure()
destination_document = Document('document.docx')
with open('document.xml') as f:
template_text = f.read()
template = Template(template_text)
template_xml = template.render(Context({'title': 'Тестовый заголовок'}))
target_xml_tree = etree.fromstring(template_xml.encode('utf-8'))
target_body = target_xml_tree[0]
root = destination_document._element
root.replace(root.body, target_body)
destination_document.save('test.docx')
Для интеграции с Django необходимо вернуть документ в виде HTTP ответа, обернув его корректными заголовками. В папке для шаблонов я создал папку docx_report скопировал туда документ и полученный из него для рекдактирования xml шаблон document.docx и document.xml
# encoding: utf-8
from StringIO import StringIO
import os
from django.template import Template, Context
from docx import Document
from lxml import etree
from django.views.generic import View
from django.http import HttpResponse
def create_docx_document(document_folder, document_context):
destination_document_file = StringIO()
destination_document = Document(os.path.join(document_folder, 'document.docx'))
with open(os.path.join(document_folder, 'document.xml')) as xml_file:
template_text = xml_file.read()
template = Template(template_text)
template_xml = template.render(Context(document_context))
target_xml_tree = etree.fromstring(template_xml.encode('utf-8'))
target_body = target_xml_tree[0]
root = destination_document._element
root.replace(root.body, target_body)
destination_document.save(destination_document_file)
return destination_document_file
class ReportView(View):
def get(self, request, *args, **kwargs):
context = {'title': 'Тестовый заголовок'}
template_dir = os.path.join(settings.BASE_DIR, 'templates', 'docx_report')
docx = create_docx_document(template_dir, context)
response = HttpResponse(
docx.getvalue(),
content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document'
)
response['Content-Disposition'] = 'attachment; filename=my_report.docx'
return response
P.S. Посвещаю эту статью своей любимой женщине, спасибо тебе, родная, за терпение и твою нежность!