""" 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: """ 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 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: _new_cell = ws.cell(start_cell.row + i, start_cell.column + j, _iter_entry.get(_iter_attr, "MISSING")) _new_cell.border = border_style j += 1 i += 1