From 9a2f753480d19b216b2fdfac0dd36a8163a10308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D0=BA=D0=BE=D1=81=20=D0=90=D1=80=D1=82=D0=B5?= =?UTF-8?q?=D0=BC=20=D0=9D=D0=B8=D0=BA=D0=BE=D0=BB=D0=B0=D0=B5=D0=B2=D0=B8?= =?UTF-8?q?=D1=87?= Date: Wed, 21 Jan 2026 09:55:55 +0700 Subject: [PATCH] Album orient by using templ. doc --- redmine_reporter/formatter_odt.py | 54 ++++++++++++++++-------------- template.odt | Bin 0 -> 8822 bytes 2 files changed, 28 insertions(+), 26 deletions(-) create mode 100644 template.odt diff --git a/redmine_reporter/formatter_odt.py b/redmine_reporter/formatter_odt.py index b6b802a..f25fd5e 100644 --- a/redmine_reporter/formatter_odt.py +++ b/redmine_reporter/formatter_odt.py @@ -1,40 +1,42 @@ from typing import List, Tuple from redminelib.resources import Issue -from odf.opendocument import OpenDocumentText -from odf.style import Style, TableProperties, TableCellProperties, ParagraphProperties +from odf.opendocument import load from odf.text import P from odf.table import Table, TableColumn, TableRow, TableCell from .formatter import get_version, hours_to_human, STATUS_TRANSLATION +import os -def format_odt(issue_hours: List[Tuple[Issue, float]]) -> OpenDocumentText: - doc = OpenDocumentText() +def format_odt(issue_hours: List[Tuple[Issue, float]]) -> "OpenDocument": + # Загружаем шаблон с альбомной ориентацией + template_path = "template.odt" + # template_path = os.path.join(os.path.dirname(__file__), "..", "template.odt") + if not os.path.exists(template_path): + raise FileNotFoundError("Шаблон template.odt не найден. Создайте его вручную в LibreOffice (альбомная ориентация) и сохраните в корень проекта.") - # Стили - table_style = Style(name="Table", family="table") - table_style.addElement(TableProperties(width="17cm", align="center")) - doc.styles.addElement(table_style) + doc = load(template_path) - cell_style = Style(name="Cell", family="table-cell") - cell_style.addElement(TableCellProperties(border="0.5pt solid #000000")) - doc.styles.addElement(cell_style) + # Стили уже есть в шаблоне — просто используем их по имени + para_style_name = "Standard" # или другое имя, если вы задали стиль в шаблоне + table_style_name = "Table1" # LibreOffice обычно даёт такое имя - para_style = Style(name="Para", family="paragraph") - para_style.addElement(ParagraphProperties(textalign="left")) - doc.styles.addElement(para_style) + # Заголовок отчёта + header_text = "Кокос Артём Николаевич. Отчет за месяц Июль." + header_paragraph = P(stylename=para_style_name, text=header_text) + doc.text.addElement(header_paragraph) # Таблица - table = Table(name="Report", stylename=table_style) - for _ in range(5): # 5 колонок + table = Table(name="Report", stylename=table_style_name) + for _ in range(5): table.addElement(TableColumn()) - # Заголовок + # Заголовки header_row = TableRow() - headers = ["Проект", "Версия", "Задача", "Статус", "Затрачено"] + headers = ["Наименование Проекта", "Номер версии*", "Задача", "Статус Готовность*", "Затрачено за отчетный период"] for text in headers: - cell = TableCell(stylename=cell_style) - p = P(stylename=para_style, text=text) + cell = TableCell() + p = P(stylename=para_style_name, text=text) cell.addElement(p) header_row.addElement(cell) table.addElement(header_row) @@ -52,19 +54,19 @@ def format_odt(issue_hours: List[Tuple[Issue, float]]) -> OpenDocumentText: display_version = version if (project != prev_project or version != prev_version) else "" row = TableRow() - for col_text in [ + cells_content = [ display_project, display_version, f"{issue.id}. {issue.subject}", status_ru, - hours_to_human(hours) - ]: - cell = TableCell(stylename=cell_style) - p = P(stylename=para_style, text=col_text) + "" # как в скриншоте + ] + for col_text in cells_content: + cell = TableCell() + p = P(stylename=para_style_name, text=col_text) cell.addElement(p) row.addElement(cell) table.addElement(row) - prev_project = project prev_version = version diff --git a/template.odt b/template.odt new file mode 100644 index 0000000000000000000000000000000000000000..44af317424181f0858e825c0141653c325a04549 GIT binary patch literal 8822 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