Reputation: 116
I was looking for a way to make merged cells filterable using openpyxl.
E.g Filtering on first name "John" in the below workbook:
Should return the first three rows:
Rather than returning only the top row (the default excel behavior):
The problem is that by default merged ranges only contain a value in the top left cell of the range: Default merged cell values
Using openpyxl is it possible to overwrite the None values within merged cells effectively making them filterable? Desired Merged cell values
For reference, you can already create filterable merged cells using macros or manual formatting as per the answer here: https://stackoverflow.com/a/49817670/14251756
I've already tried overwriting the none values using the below script but get the error AttributeError: 'MergedCell' object attribute 'value' is read-only
import openpyxl
wb = openpyxl.load_workbook("Workbook")
ws = wb['Worksheet']
#Get list of ranges of merged cells
merged_cell_ranges = ws.merged_cells.ranges
merged_cell_ranges = list(map(str,merged_cell_ranges))
#Iterate through ranges and write top merged cell value to empty cells
for merged_range in merged_cell_ranges:
for row in ws[merged_range]:
for cell in row:
cell.value = ws[merged_range.split(':')[0]].value
Upvotes: 0
Views: 980
Reputation: 46
Far from ideal, but I achieved the desired result by modifying the openpyxl source.
The function to modify is located here: https://foss.heptapod.net/openpyxl/openpyxl/-/blob/branch/3.0/openpyxl/worksheet/worksheet.py#L586
you can simply add an argument to choose whether or not the cells become merged cells and therefor "cleaned".
The modified function now looks like this:
def merge_cells(self, range_string=None, start_row=None, start_column=None, end_row=None, end_column=None, clean=True):
""" Set merge on a cell range. Range is a cell range (e.g. A1:E1) """
if range_string is None:
cr = CellRange(range_string=range_string, min_col=start_column, min_row=start_row,
max_col=end_column, max_row=end_row)
range_string = cr.coord
mcr = MergedCellRange(self, range_string)
self.merged_cells.add(mcr)
if clean:
self._clean_merge_range(mcr)
else:
mcr.format()
Simply add clean=False to keep the values.
ws.merge_cells('A2:A9', clean=False)
Upvotes: 3