2021-10-21 14:58:54 +02:00
|
|
|
"""
|
|
|
|
Author: Michel Peltriaux
|
|
|
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
|
|
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
|
|
|
Created on: 21.10.21
|
|
|
|
|
|
|
|
"""
|
|
|
|
from django.core.files.temp import NamedTemporaryFile
|
|
|
|
from openpyxl import load_workbook
|
|
|
|
|
|
|
|
|
|
|
|
class TempExcelFile:
|
|
|
|
""" A temporary excel sheet which will not be saved on the hard drive permanently.
|
|
|
|
|
|
|
|
Using a template file and a template_map dictionary, this class can be used to fill in automatically
|
|
|
|
predefined values into certain cells.
|
|
|
|
|
|
|
|
Can be used to create excel files from data and sending it as a response like
|
|
|
|
_file = TempExcelFile()
|
|
|
|
response = HttpResponse(
|
|
|
|
content=file.stream,
|
|
|
|
content_type="application/ms-excel",
|
|
|
|
)
|
|
|
|
response['Content-Disposition'] = 'attachment; filename=my_file.xlsx'
|
|
|
|
return response
|
|
|
|
|
|
|
|
"""
|
|
|
|
stream = None
|
|
|
|
_template_file_path = None
|
|
|
|
_template_map = {}
|
|
|
|
_data_obj = None
|
|
|
|
|
|
|
|
def __init__(self, template_file_path: str = None, template_map: dict = None):
|
|
|
|
self._template_map = template_map or {}
|
|
|
|
self._template_file_path = template_file_path
|
|
|
|
self._workbook = load_workbook(template_file_path)
|
|
|
|
self._file = NamedTemporaryFile()
|
|
|
|
|
|
|
|
self._replace_template_placeholders()
|
|
|
|
|
|
|
|
def _replace_template_placeholders(self, start_row: int = 0):
|
|
|
|
""" Replaces all placeholder inside the template according to _template_map
|
|
|
|
|
|
|
|
Args:
|
|
|
|
start_row (int): Defines where to start
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
2021-10-22 12:36:39 +02:00
|
|
|
sheets = self._workbook.worksheets
|
|
|
|
for sheet in sheets:
|
|
|
|
ws = sheet
|
|
|
|
# Always activate sheet protection
|
|
|
|
ws.protection.sheet = True
|
|
|
|
ws.protection.enable()
|
|
|
|
_rows = ws.iter_rows(start_row)
|
|
|
|
for row in _rows:
|
|
|
|
for cell in row:
|
|
|
|
val = cell.value
|
|
|
|
if val in self._template_map:
|
|
|
|
attr = self._template_map[val]
|
|
|
|
# If keyword '_iter' can be found inside the placeholder value it's an iterable and we
|
|
|
|
# need to process it differently
|
|
|
|
if isinstance(attr, dict):
|
|
|
|
# Read the iterable object and related attributes from the dict
|
|
|
|
_iter_obj = attr.get("iterable", None)
|
|
|
|
_attrs = attr.get("attrs", [])
|
|
|
|
self._add_cells_from_iterable(ws, cell, _iter_obj, _attrs)
|
|
|
|
# Since the sheet length did change now, we need to rerun this function starting with the new
|
|
|
|
# row counter
|
|
|
|
self._replace_template_placeholders(start_row=cell.row + len(_iter_obj))
|
|
|
|
else:
|
|
|
|
cell.value = attr
|
2021-10-21 14:58:54 +02:00
|
|
|
self._workbook.save(self._file.name)
|
|
|
|
self._file.seek(0)
|
|
|
|
self.stream = self._file.read()
|
|
|
|
|
|
|
|
def _add_cells_from_iterable(self, ws, start_cell, _iter_obj: iter, _attrs: list):
|
|
|
|
"""
|
|
|
|
Adds iterable data defined by _template_map like
|
|
|
|
...
|
|
|
|
"some_placeholder_iter": {
|
|
|
|
"iterable": iterable_object,
|
|
|
|
"attrs": [
|
|
|
|
"attr1",
|
|
|
|
"attr2",
|
|
|
|
"attr3",
|
|
|
|
...
|
|
|
|
]
|
|
|
|
},
|
|
|
|
...
|
|
|
|
|
|
|
|
Args:
|
|
|
|
ws (Workbook): The active workbook
|
|
|
|
_iter_obj (dict): Iterable definitions from template_map
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
|
|
|
# Save border style
|
|
|
|
border_style = start_cell.border.copy()
|
|
|
|
# Drop current row, since it is just placeholder
|
|
|
|
ws.delete_rows(start_cell.row)
|
|
|
|
# Add enoug empty rows for the data
|
|
|
|
ws.insert_rows(start_cell.row, len(_iter_obj))
|
|
|
|
|
|
|
|
i = 0
|
|
|
|
for _iter_entry in _iter_obj:
|
|
|
|
j = 0
|
|
|
|
for _iter_attr in _attrs:
|
2022-06-21 14:19:10 +02:00
|
|
|
_new_cell = ws.cell(start_cell.row + i, start_cell.column + j, _iter_entry.get(_iter_attr, "MISSING"))
|
2021-10-21 14:58:54 +02:00
|
|
|
_new_cell.border = border_style
|
|
|
|
j += 1
|
|
|
|
i += 1
|