简体   繁体   中英

openpyxl: How can I use lists with Data Validation when creating Excel worksheets using Python?

I'm still pretty new to using Python... and I'm trying to work out how to apply Data Validation to a region of an Excel worksheet using a named range of cells... amongst other things.

For the code below, I'm looking to find out how to:-

  • use a list and/or a named range for the forumla1 item in DataValidation()

...as well as finding better ways to:-

  • build a range specification, like I've used in DefinedName() attr_text and when applying a validation object to a (named) range

I've done much with Google search and Stack Overflow... but without much success. I've also been through the relevant parts of https://openpyxl.readthedocs.io/en/stable/ and whilst the examples have been helpful, the actual definitions, etc of the objects, their usage, etc have been less so.

I'm using (Anaconda) Python v3.6.5 under Windows 10 64-bit, openpyxl 3.0.3.

I'd appreciate any pointers or suggestions...

The code:


import openpyxl
from openpyxl import Workbook
from openpyxl.worksheet.datavalidation import DataValidation

# ---- Create the 'Data' worksheet - to set-up defined ranges

data_headers = [ "StatusValues", "ActivityValues" ]
status_values = [ "ROOKIE", "ACTIVE", "SUSPENDED", "RETIRED" ]
activity_values = [ "ASSIGNED", "TRAINING", "TRAVELLING" ]

# ----

wb = Workbook()
ws_name = wb.create_sheet('Data')              # Does NOT make it active (current)
ws = wb.get_sheet_by_name('Data')

ws.append(data_headers)                        # Create header line
for idx, row in enumerate(status_values):      # Populate the worksheet with 'Status' items...
   ws.cell(column=1, row=idx+2, 
                  value=status_values[idx])

for idx, row in enumerate(activity_values):    # ...and 'Activity' items
   ws.cell(column=2, row=idx+2, 
                  value=activity_values[idx])

# ---- Set-up the named ranges for the entire workbook
#      There HAS to be a BETTER way of specifying the 'worksheet:range' thing(!)

lo = 2
hi = len(status_values) + 1
sbuf = 'Data!$A$%s:$A$%s'%(lo, hi)
sv_range = openpyxl.workbook.defined_name.DefinedName('StatusValues', attr_text=sbuf)
wb.defined_names.append(sv_range)

lo = 2
hi = len(activity_values) + 1
sbuf = 'Data!$B$%s:$B$%s'%(lo, hi)
av_range = openpyxl.workbook.defined_name.DefinedName('ActivityValues', attr_text=sbuf)
wb.defined_names.append(av_range)

# ---- Get some data into the 'main' worksheet

ws = wb.worksheets[0]   # Use the 'original' worksheet
ws.title = "Operatives"
ws.append(["Beast", "Status", "Activity"])     
ws["A2"] = "Dog"
ws["A3"] = "Cat"
ws["A4"] = "Wombat"
ws["A5"] = "Serval"
ws["A6"] = "Camel"

# Create a data-validation object with list validation

dv = DataValidation(type="list", 
                    formula1='"ROOKIE,ACTIVE,SUSPENDED,RETIRED"',  # How can I use 'status_values'...
                    allow_blank=True)

dv2 = DataValidation(type="list", 
                    formula1='"ASSIGNED,TRAINING,TRAVELLING"',     # ...and 'activity_values' lists?
                    allow_blank=True)


# Add the data validation objects to the worksheet
ws.add_data_validation(dv)
ws.add_data_validation(dv2)

# ...and to specific range (ugh... so ugly.. again, has to be a better way..)
dv_app = "B2:B" + str(ws.max_row)
dv.add(dv_app)

dv_app = "C2:C" + str(ws.max_row)
dv2.add(dv_app)

# ----

wb.save(filename = 'validation.xlsx')

exit(0)

I have the same problem. You code, Skeeve, was very helpful and I believe that, after 9 months you have found a solution to use the list name as argument to formula1 parameter. Thanks

Actually, I found a way:

str1 = ','.join(status_values)                                    
str1 = '"'+str1+'"'                                               
                                                                                                       
dv = DataValidation(type="list", formula1=str1, allow_blank=True)

There is also an alternative way of doing this. You can provide a range address eg =Data:$A$2.$A$5 as the argument to the formula1 parameter (and remember that this does not need double quotes around it).

Linking to a range prevents bloating of your spreadsheet in case there is a large number of items in that list and you are doing data validation on a lot of cells. It will also take care of a scenario where a user updates one of the values on the Data tab of the spreadsheet

To make this work, I updated the code that Skeeve had posted as follows:

  • replaced his sbuf variable with subf1 and sbuf2 as we have two different data ranges (makes it a little easier to follow)
  • inserted a = before sbuf1 and sbuf2 in the argument for formula1
  • Also ws = wb.get_sheet_by_name('Data') is now deprecated so replaced that with ws = wb['Data']
import openpyxl
from openpyxl import Workbook
from openpyxl.worksheet.datavalidation import DataValidation

# ---- Create the 'Data' worksheet - to set-up defined ranges

data_headers = [ "StatusValues", "ActivityValues" ]
status_values = [ "ROOKIE", "ACTIVE", "SUSPENDED", "RETIRED" ]
activity_values = [ "ASSIGNED", "TRAINING", "TRAVELLING" ]

# ----

wb = Workbook()
ws_name = wb.create_sheet('Data')              # Does NOT make it active (current)
ws = wb['Data']

ws.append(data_headers)                        # Create header line
for idx, row in enumerate(status_values):      # Populate the worksheet with 'Status' items...
   ws.cell(column=1, row=idx+2, 
                  value=status_values[idx])

for idx, row in enumerate(activity_values):    # ...and 'Activity' items
   ws.cell(column=2, row=idx+2, 
                  value=activity_values[idx])

# ---- Set-up the named ranges for the entire workbook
#      There HAS to be a BETTER way of specifying the 'worksheet:range' thing(!)

lo = 2
hi = len(status_values) + 1
sbuf1 = 'Data!$A$%s:$A$%s'%(lo, hi)
sv_range = openpyxl.workbook.defined_name.DefinedName('StatusValues', attr_text=sbuf1)
wb.defined_names.append(sv_range)

lo = 2
hi = len(activity_values) + 1
sbuf2 = 'Data!$B$%s:$B$%s'%(lo, hi)
av_range = openpyxl.workbook.defined_name.DefinedName('ActivityValues', attr_text=sbuf2)
wb.defined_names.append(av_range)

# ---- Get some data into the 'main' worksheet

ws = wb.worksheets[0]   # Use the 'original' worksheet
ws.title = "Operatives"
ws.append(["Beast", "Status", "Activity"])     
ws["A2"] = "Dog"
ws["A3"] = "Cat"
ws["A4"] = "Wombat"
ws["A5"] = "Serval"
ws["A6"] = "Camel"

# Create a data-validation object with list validation

dv = DataValidation(type="list", 
                    formula1='=' + sbuf1,  
                    allow_blank=True)

dv2 = DataValidation(type="list", 
                    formula1='=' + sbuf2,    
                    allow_blank=True)


# Add the data validation objects to the worksheet
ws.add_data_validation(dv)
ws.add_data_validation(dv2)

# ...and to specific range (ugh... so ugly.. again, has to be a better way..)
dv_app = "B2:B" + str(ws.max_row)
dv.add(dv_app)

dv_app = "C2:C" + str(ws.max_row)
dv2.add(dv_app)

# ----

wb.save(filename = 'validation.xlsx')

exit(0)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM