DocX-export added - not perfect yet.
This commit is contained in:
207
dokumente/docx_utils.py
Normal file
207
dokumente/docx_utils.py
Normal file
@@ -0,0 +1,207 @@
|
||||
"""
|
||||
Utility functions for exporting Dokument instances as Word (.docx) files.
|
||||
"""
|
||||
import os
|
||||
from docx import Document
|
||||
from docx.shared import Pt
|
||||
from docx.oxml.ns import qn
|
||||
|
||||
|
||||
TEMPLATE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'docx_template', 'template.docx')
|
||||
|
||||
|
||||
def build_standard_docx(dokument):
|
||||
"""
|
||||
Generate a Word Document object for the given Dokument.
|
||||
|
||||
Opens the template .docx to inherit custom styles (Hermes, Kurztext,
|
||||
Tabelle, etc.), clears the body, then writes the document structure.
|
||||
|
||||
Args:
|
||||
dokument: a Dokument instance (should be prefetched with related data)
|
||||
|
||||
Returns:
|
||||
docx.Document instance ready to be saved
|
||||
"""
|
||||
doc = Document(TEMPLATE_PATH)
|
||||
|
||||
# Clear all body content while preserving page-layout section properties
|
||||
body = doc.element.body
|
||||
for element in list(body):
|
||||
if element.tag != qn('w:sectPr'):
|
||||
body.remove(element)
|
||||
|
||||
# 1. Title
|
||||
title_para = doc.add_paragraph(style='Normal')
|
||||
run = title_para.add_run(f'Standard\nIT-Sicherheit-{dokument.name}')
|
||||
run.bold = True
|
||||
run.font.size = Pt(22)
|
||||
|
||||
# 2. Metadata table (uses the "Hermes" table style from the template)
|
||||
meta_table = doc.add_table(rows=0, cols=2, style='Hermes')
|
||||
meta_rows = [
|
||||
('Identifikation:', dokument.nummer),
|
||||
('Dokumentationsklasse:', dokument.dokumententyp.name if dokument.dokumententyp else ''),
|
||||
('Gültig ab:', dokument.gueltigkeit_von.strftime('%d.%m.%Y') if dokument.gueltigkeit_von else ''),
|
||||
('Gültig bis:', dokument.gueltigkeit_bis.strftime('%d.%m.%Y') if dokument.gueltigkeit_bis else ''),
|
||||
('Klassifizierung:', ''),
|
||||
('Verantwortliche Stelle:', 'Information Security Management BIT'),
|
||||
('Autoren:', ', '.join(a.name for a in dokument.autoren.all())),
|
||||
('Prüfende:', ', '.join(p.name for p in dokument.pruefende.all())),
|
||||
]
|
||||
for label, value in meta_rows:
|
||||
row = meta_table.add_row()
|
||||
row.cells[0].text = label
|
||||
row.cells[1].text = value
|
||||
|
||||
# 3. Einleitung
|
||||
doc.add_heading('Einleitung', level=1)
|
||||
for abschnitt in dokument.einleitung_set.order_by('order'):
|
||||
_add_abschnitt(doc, abschnitt)
|
||||
|
||||
# 4. Geltungsbereich
|
||||
doc.add_heading('Geltungsbereich', level=1)
|
||||
for abschnitt in dokument.geltungsbereich_set.order_by('order'):
|
||||
_add_abschnitt(doc, abschnitt)
|
||||
|
||||
# 5. Vorgaben
|
||||
doc.add_heading('Vorgaben', level=1)
|
||||
|
||||
vorgaben = list(
|
||||
dokument.vorgaben
|
||||
.order_by('thema__name', 'nummer')
|
||||
.select_related('thema')
|
||||
.prefetch_related(
|
||||
'vorgabekurztext_set__abschnitttyp',
|
||||
'vorgabelangtext_set__abschnitttyp',
|
||||
'checklistenfragen',
|
||||
'referenzen',
|
||||
)
|
||||
)
|
||||
|
||||
current_thema = None
|
||||
for vorgabe in vorgaben:
|
||||
thema_name = vorgabe.thema.name if vorgabe.thema else ''
|
||||
if thema_name != current_thema:
|
||||
current_thema = thema_name
|
||||
doc.add_heading(thema_name, level=2)
|
||||
|
||||
doc.add_heading(f'{vorgabe.Vorgabennummer()} \u2013 {vorgabe.titel}', level=4)
|
||||
|
||||
for kt in vorgabe.vorgabekurztext_set.order_by('order'):
|
||||
_add_abschnitt(doc, kt, default_style='Kurztext')
|
||||
|
||||
for lt in vorgabe.vorgabelangtext_set.order_by('order'):
|
||||
_add_abschnitt(doc, lt)
|
||||
|
||||
fragen = list(vorgabe.checklistenfragen.all())
|
||||
if fragen:
|
||||
doc.add_paragraph('Checklistenfragen', style='Normal')
|
||||
for frage in fragen:
|
||||
doc.add_paragraph(frage.frage, style='Normal')
|
||||
|
||||
refs = list(vorgabe.referenzen.all())
|
||||
if refs:
|
||||
doc.add_paragraph('Referenzen: ' + ', '.join(r.Path() for r in refs), style='Normal')
|
||||
|
||||
# 6. Checkliste
|
||||
doc.add_heading('Checkliste', level=1)
|
||||
|
||||
all_fragen = [
|
||||
(vorgabe.Vorgabennummer(), frage.frage)
|
||||
for vorgabe in vorgaben
|
||||
for frage in vorgabe.checklistenfragen.all()
|
||||
]
|
||||
if all_fragen:
|
||||
checklist_table = doc.add_table(rows=1, cols=3, style='Normal Table')
|
||||
header = checklist_table.rows[0].cells
|
||||
header[0].text = '#'
|
||||
header[1].text = 'Bezeichnung (WAS)'
|
||||
header[2].text = 'Richtlinieregel (WIE)'
|
||||
for vorgabe_num, frage_text in all_fragen:
|
||||
row = checklist_table.add_row()
|
||||
row.cells[0].text = vorgabe_num
|
||||
row.cells[1].text = ''
|
||||
row.cells[2].text = frage_text
|
||||
|
||||
# 7. Changelog
|
||||
changelog_entries = list(
|
||||
dokument.changelog.order_by('-datum').prefetch_related('autoren')
|
||||
)
|
||||
if changelog_entries:
|
||||
doc.add_heading('Änderungskontrolle', level=1)
|
||||
changelog_table = doc.add_table(rows=1, cols=4, style='Tabelle')
|
||||
header = changelog_table.rows[0].cells
|
||||
header[0].text = 'Wann'
|
||||
header[1].text = 'Version'
|
||||
header[2].text = 'Wer'
|
||||
header[3].text = 'Beschreibung'
|
||||
for cl in changelog_entries:
|
||||
row = changelog_table.add_row()
|
||||
row.cells[0].text = cl.datum.strftime('%d.%m.%Y') if cl.datum else ''
|
||||
row.cells[1].text = ''
|
||||
row.cells[2].text = ', '.join(a.name for a in cl.autoren.all())
|
||||
row.cells[3].text = cl.aenderung
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
def _add_abschnitt(doc, abschnitt, default_style='Normal'):
|
||||
"""
|
||||
Add a single Textabschnitt to the document, respecting its section type.
|
||||
"""
|
||||
typ = abschnitt.abschnitttyp.abschnitttyp if abschnitt.abschnitttyp else 'text'
|
||||
inhalt = abschnitt.inhalt or ''
|
||||
|
||||
if not inhalt.strip():
|
||||
return
|
||||
|
||||
if typ == 'text':
|
||||
doc.add_paragraph(inhalt, style=default_style)
|
||||
|
||||
elif typ in ('liste ungeordnet', 'liste geordnet'):
|
||||
for line in inhalt.strip().split('\n'):
|
||||
line = line.strip().lstrip('- ').lstrip('* ')
|
||||
if line:
|
||||
doc.add_paragraph(line, style='Normal')
|
||||
|
||||
elif typ == 'tabelle':
|
||||
_add_markdown_table(doc, inhalt)
|
||||
|
||||
else:
|
||||
doc.add_paragraph(inhalt, style='Normal')
|
||||
|
||||
|
||||
def _add_markdown_table(doc, markdown_content):
|
||||
"""
|
||||
Parse a markdown table and add it to the document as a Word table.
|
||||
Falls back to a plain paragraph if parsing fails.
|
||||
"""
|
||||
lines = [line.strip() for line in markdown_content.strip().split('\n') if line.strip()]
|
||||
if len(lines) < 2:
|
||||
doc.add_paragraph(markdown_content, style='Normal')
|
||||
return
|
||||
|
||||
header_cells = [c.strip() for c in lines[0].split('|') if c.strip()]
|
||||
if not header_cells:
|
||||
doc.add_paragraph(markdown_content, style='Normal')
|
||||
return
|
||||
|
||||
# Skip the separator row (dashes), collect data rows
|
||||
data_lines = [
|
||||
line for line in lines[2:]
|
||||
if not all(c in '-|: ' for c in line)
|
||||
]
|
||||
|
||||
table = doc.add_table(rows=1, cols=len(header_cells), style='Table Grid')
|
||||
header_row = table.rows[0].cells
|
||||
for i, cell_text in enumerate(header_cells):
|
||||
if i < len(header_row):
|
||||
header_row[i].text = cell_text
|
||||
|
||||
for line in data_lines:
|
||||
row_cells = [c.strip() for c in line.split('|') if c.strip()]
|
||||
row = table.add_row()
|
||||
for i, cell_text in enumerate(row_cells):
|
||||
if i < len(row.cells):
|
||||
row.cells[i].text = cell_text
|
||||
Reference in New Issue
Block a user