До смешного простой код, который может сэкономить вам кучу времени в будущем. И как фанат автоматизации, который старается избегать любой однообразной и рутинной работы, я от таких решений просто в восторге. Про проекты в этой теме кстати рассказываю здесь.

Впервые с такой задачей я столкнулась много лет назад, когда ко мне пришла моя арендодательница, у которой я снимала квартиру в Питере, с запросом из серии "ну тыж программист" (хотя тогда я еще работала инженером и у меня не было каких-то супер знаний в разработке, так что могу точно заверить, что с этим кодом справится практически любой начинающий разработчик). Она занималась организацией детских танцевальных конкурсов регионального (или выше) уровня и на каждые соревнования нанимала отдельного человека, который в течение двух дней печатал дипломы. Детей были сотни, дипломов тоже, и чисто из-за человеческого фактора естественно и брака было довольного много. И она попросила как-то это автоматизировать (тыж программист).

Технически задача выглядела так: по итогам каждого этапа соревнований формировалась экселевская табличка, каждая строчка которой содержала информацию о ребенке/танцевальном коллективе, руководителе, номинации, программе и месте (диплом 1, 2 или 3 степени). Соотвественно, по данным из каждой строчки нужно было сформировать отдельный диплом по шаблону. Шаблон же представлял из себя обычный вордовский документ, куда в определённые места нужно вставить данные из таблицы.

Задача типичная для многих и когда я создавала свой курс по автоматизации рутинных задач на python, я вспомнила про эту историю и посвятила этому одну практическую работу, которую вы, собственно, ниже и увидите. Данными талантливых детей я конечно же светить не буду, но для своих слушателей я целый вечер придумывала базу данных с воображаемыми сотрудниками, которым нужно нагенерировать заявлений на отпуск. Задача полностью аналогичная: таблица Excel с информацией о сотрудниках и датами отпусков, шаблон заявления в Word. Демонстрация ниже.

Шаблон заявления на отпуск в Word
Таблица с данными о сотрудниках xlsx

Все данные были придуманы из головы, совпадения с реальными лицами абсолютно случайны. Адреса электронных почт я также придумала, многие введены специально некорректно для практики по регулярным выражениям на моем курсе.

Подготовка шаблона

Для подстановки данных в шаблон .docx используется библиотека python-docx-template.

Библиотека docxtpl используется для работы с документами формата Microsoft Word (.docx) в Python, которая позволяет создавать и редактировать документы на основе шаблонов. Она позволяет автоматизировать процесс создания документов, вставляя переменные данные в предварительно созданные шаблоны.

Принцип работы с библиотекой docxtpl:

  1. Шаблоны документов: Вы создаете шаблон документа в формате .docx с помощью Microsoft Word или другого редактора. В шаблоне вы можете определить места, где будут вставляться переменные данные.

  2. Использование переменных: В шаблоне вы определяете переменные, которые будут заполняться данными из вашего приложения или источника данных. Например, в шаблоне вы можете указать "{{ имя_пользователя }}", а потом вставить реальное имя пользователя в это место.

  3. Генерация документов: После того, как шаблон создан и заполнен переменными, вы можете использовать библиотеку docxtpl для генерации документа на основе этого шаблона. Она автоматически заменит переменные в шаблоне на соответствующие данные.

Теперь рассмотрим все это на примере. Сначала шаблон нужно подготовить, то есть указать места в документе, куда мы будем подставлять данные.

Как это сделать: в те места в нашем шаблоне, куда будут подставляться какие-то данные, мы вставляем двойные фигурные скобки и название переменной, которую далее подставим. Пример ниже.

Форматирование мы задаем не в коде, а в самом шаблоне. То есть все, что связано с шрифтом, размером, отступами и тд - это все ручками сами указываем в шаблоне документа. Естественно, это можно сделать и в коде, но в этой статье мы этого делать не будем, а только подставим данные.

Готовый шаблон

Код для подстановки каких-то данных в шаблон до смешного прост.

Установка библиотеки:

pip install docxtpl

Код:

# Импортируем нужный объект из библиотеки 
from docxtpl import DocxTemplate

# Загрузка шаблона
doc = DocxTemplate("шаблон.docx")

# Данные для заполнения шаблона
context = {
    'company': 'Моя любимая работа',
    'name': 'Иван',
    'last_name': 'Иванов',
    'start_data': '10.03.2024',
    'end_data': '24.03.2024'
}

# Заполнение шаблона данными
doc.render(context)

# Сохранение документа
doc.save("новый_документ.docx")

Давайте пошагово разберем, что тут происходит:

  1. from docxtpl import DocxTemplate: Импортируем класс DocxTemplate из библиотеки docxtpl, который позволяет работать с .docx файлами и заполнять их данными из словаря.

  2. doc = DocxTemplate("шаблон.docx"): Загружаем шаблон документа из файла "шаблон.docx" и создаем объект doc, который представляет этот документ.

  3. context = { ... }: Создаем словарь context, содержащий данные для заполнения шаблона. Ключи словаря соответствуют переменным в шаблоне, которые будут заменены на соответствующие значения. Обратите на это внимание.

  4. doc.render(context): Заполняем шаблон данными из словаря context. Это заменит переменные в шаблоне на соответствующие значения.

  5. doc.save("новый_документ.docx"): Сохраняем заполненный документ в новый файл с именем "новый_документ.docx".

Таким образом, данный код берет шаблон документа, заполняет его данными из словаря context и сохраняет результат в новом файле .docx.

Запускаем код и видим результат

Подключаем таблицу

OpenPyXL - это библиотека для работы с файлами формата Microsoft Excel (.xlsx)

Установка

 pip install openpyxl

Основные возможности

  • Чтение данных: OpenPyXL позволяет считывать данные из существующих файлов Excel.

  • Запись данных: Библиотека позволяет создавать новые файлы Excel и записывать данные в эти файлы.

  • Модификация данных: Вы можете изменять существующие данные в таблицах Excel, добавлять новые строки, изменять форматирование и т.д.

  • Работа с формулами: OpenPyXL поддерживает использование формул Excel, позволяя вычислять значения на основе других ячеек.

Какие функции этой библиотеки нам сейчас понадобятся:

wb = openpyxl.load_workbook(filename='people_data_vacation.xlsx')
  1. load_workbook: Это функция из библиотеки openpyxl, которая загружает рабочую книгу (workbook) из файла Excel.

  2. filename='people_data_vacation.xlsx': Этот аргумент функции load_workbook указывает путь к файлу, который нужно загрузить. В данном случае, это файл "people_data_vacation.xlsx".

После выполнения этой строки кода переменная wb будет содержать объект рабочей книги (workbook), который представляет содержимое файла "people_data_vacation.xlsx". Далее мы можем использовать этот объект для доступа к содержимому файла Excel, такому как рабочие листы (worksheets) и их содержимое. Что мы дальше и сделаем.

sheet = wb['vacation']

Эта строчка кода извлекает рабочий лист (worksheet) из рабочей книги (workbook) wb и присваивает его переменной sheet. В данном случае, мы извлекаем лист с именем 'vacation'. Если такого листа не существует, будет вызвано исключение.

Далее из этого листа мы уже можем извлекать данные (из конкретных ячеек).

Посмотрим внимательно на одну строку из таблицы. Придумаем алгоритм для одного сотрудника и дальше уже не составит труда в цикле применить его для остальных.

Например, нам нужно заполнить заявление для первого сотрудника - вторая строчка таблицы. Соотвественно, нужно достать данные из ячеек A2(фамилия), B2(имя), D2(компания), F2(начало отпуска) и G2(конец отпуска).

Достать данные из конкретной ячейки можно следующей командой

sheet['A2'].value

После выполнения этой строки кода будет возвращено значение, которое содержится в ячейке A2 рабочего листа sheet.

Тогда для остальных ячеек это будет выглядеть так:

name = sheet['B2'].value
last_name = sheet['A2'].value
company = sheet['D2'].value
start_data = sheet['F2'].value.date()
end_data = sheet['G2'].value.date()

К датам еще добавляю функцию .date(), чтобы она возвращалась не в формате '2022-05-01 00:00:00', а хотя бы 2022-05-01.

Дополняем код, написанный в прошлой части для формирования заявления с данными из таблицы для первого сотрудника

import openpyxl
from docxtpl import DocxTemplate

doc = DocxTemplate("шаблон.docx")

wb = openpyxl.load_workbook(filename='people_data_vacation.xlsx')
sheet = wb['vacation']

name = sheet['B2'].value
last_name = sheet['A2'].value
company = sheet['D2'].value
start_data = sheet['F2'].value.date()
end_data = sheet['G2'].value.date()

# Данные для заполнения шаблона
context = {
    'company': company,
    'name': name,
    'last_name': last_name,
    'start_data': start_data,
    'end_data': end_data  
    }

# Заполнение шаблона данными
doc.render(context)

# Сохранение документа
doc.save("новый_документ.docx")

Обратите внимание, что я заменила данные в словаре context на переменные. Можно было бы сделать и без промежуточных переменных и сразу сформировать словарь, но в перспективе вам может понадобиться добавить какие-то проверки, что в словаре сделать не получится.

Получается, что код для первого сотрудника готов, осталось добавить цикл и нагенерировать заявлений для всех остальных. По сути, единственное, что будет меняться в коде для каждого сотрудника это индекс ячеек, поэтому давайте привяжем его к счётчику в цикле.

for num in range(2,6):
    
    name = sheet['B'+str(num)].value
    last_name = sheet['A'+str(num)].value
    company = sheet['D'+str(num)].value
    start_data = sheet['F'+str(num)].value.date()
    end_data = sheet['G'+str(num)].value.date()
    
    context = {
    'company': company,
    'name': name,
    'last_name': last_name,
    'start_data': start_data,
    'end_data': end_data  
    }
    
    doc.render(context)
    doc.save(last_name+' заявление на отпуск.docx')

Конструкция ['B'+str(num)] будет по очереди возвращать B2, B3, B4 и тд.

Еще обратите внимание, что для каждого нового документа должно формироваться новое название, я для удобства сразу добавляю фамилию doc.save(last_name+' заявление на отпуск.docx'), чтобы в итоге документы выглядели так

range(2,6) - начинаем с индекса 2, так как первая строчка таблицы это заголовки, а до 6 чисто для тестирования кода, потому что если вы допустите ошибку, то будет проще удалить несколько тестовых заявлений, чем пару сотен. Если несколько тестовых документов сформировались корректно, то уже можно заменить конечное число на нужное количество (либо вручную, либо использовать len(list(sheet.rows)+1)

Итоговый код

import openpyxl
from docxtpl import DocxTemplate

wb = openpyxl.load_workbook(filename='people_data_vacation.xlsx')
sheet = wb['vacation']

doc = DocxTemplate('шаблон.docx')

for num in range(2,len(list(sheet.rows))+1):
    
    name = sheet['B'+str(num)].value
    last_name = sheet['A'+str(num)].value
    company = sheet['D'+str(num)].value
    start_data = sheet['F'+str(num)].value.date()
    end_data = sheet['G'+str(num)].value.date()
    
    context = {
    'company': company,
    'name': name,
    'last_name': last_name,
    'start_data': start_data,
    'end_data': end_data  
    }
    
    doc.render(context)
    doc.save(last_name+' заявление на отпуск.docx')

Запускаем код и наслаждаемся результатом.

В своем канале IT как цифровое творчество пишу про автоматизацию, разные интересные обучения, создание курсов, рассказываю про свой опыт и о том, как я от инженера на заводе дошла до создания IT-стартапа, даю рекомендации и анонсирую запуски интересных проектов по разработке. 

Надеюсь, что материал был вам полезен! И успехов в ваших начинаниях!