在一个B/S系统中,用户经常需要从以Excel的方式填写后导入,此时网页上需要提供Excel填写的模板,将所需填写的字段以及所处的列的位置固定下来。其中,避免用户随意填写导致系统后台校验出错,对于某些字段,系统需要预设好固定的选项值,用户只能在给定的范围内选择,不允许人工输入填写。
如果字段值的数据范围长期不变,则可直接固化在Excel中;如果字段值偶尔会更新,需要在下载Excel模板时,实时从数据库获取,则需要动态生成该Excel模板。
静态Excel模板
制作Excel模板
新建Excel文件,设置好表头名称,从第二行开始设置数据校验。具体步骤为:
数据》数据验证》设置,“允许”选择“序列”,“来源”字段可手动输入文本(以英文逗号分隔)或选中任意sheet的任意表格内容。
提供下载
from flask import current_app, request, send_from_directory, send_file
def download_excel_template():
"""
下载Excel模板
"""
file_dir = os.path.join(current_app.config['LIB_DIR'], 'files', 'templates') # 模板存放目录
current_app.logger.info("下载%s下的文件:%s" % (file_dir, 'Template.xlsx'))
return send_from_directory(file_dir, 'Template.xlsx', as_attachment=True)
动态Excel模板
更特殊的场景下,Excel单元格下拉列表的内容需要从系统中动态获取。例如,用户希望通过填写并上传Excel到某系统,由系统在openstack平台上创建一台虚拟机。虚机的hostname、ip等内容需要由用户自行指定,但虚机所使用的Flavor(即CPU/内存)、镜像(image)、卷的类型(volume type)、网络(network)、子网(subnet)等,名称较为复杂不便记忆,最好是能提供Excel的下拉选择功能。image等信息需要从openstack平台拉取,且管理员随时都有可能会在openstack平台上进行增删改的操作,因此为保持Excel模板的准确性、避免经常性的更新维护,可以动态Excel模板的方式提供下载。
这里,推荐使用xlsxwriter模块生成Excel文件。[Creating Excel files with Python and XlsxWriter — XlsxWriter Documentation]
确定Excel的列名
header_row = ['hostname', 'flavor', 'image']
获取下拉选项数据
flavors = get_openstack_flavor_names()
images = get_openstack_image_names()
列表直接写在文本中
通用函数
# chr(i) 65-90 # A,B,C ... Z
def get_column(idx):
"""
根据数字序号,获取Excel的列名,从0开始
例如,0->A
"""
prefix = int(idx/26)
suffix = idx%26
if not prefix:
return chr(suffix+65)
else:
return get_column(prefix)+chr(suffix+65)
def get_excel_cell_name(row_idx, col_idx):
"""
根据行列的序号,获取单元格的名称,从(0,0)开始
例如,(0,0)->A1
"""
if row_idx < 0:
raise ValueError("Excel的行号必须大于等于0")
if col_idx < 0:
raise ValueError("Excel的列号必须大于等于0")
return "%s%s"%(get_column(col_idx), row_idx+1)
生成Excel
对于单元格的数据校验,可以使用其data_validation功能。Working with Data Validation — XlsxWriter Documentation
import xlsxwriter
from flask import current_app
def generate_excel_template():
newfile = os.path.join(current_app.config['TMP_DIR'], 'download', 'Template-%s.xlsx' % (time.strftime("%Y%m%d%H%M")))
workbook = xlsxwriter.Workbook(newfile)
sheet = workbook.add_worksheet("Sheet1")
row = 0
# 表头
for idx, header in enumerate(header_row):
sheet.write(row, idx, header)
row += 1
# flavor
flavors = get_openstack_flavor_names() # todo,从数据库获取数据列表
cellname = get_excel_cell_name(row, header_row.index("flavor"))
sheet.data_validation(cellname, {"validate": "list", "source": flavors})
# image
images = get_openstack_image_names() #todo,从数据库获取数据列表
cellname = get_excel_cell_name(row, header_row.index("image"))
sheet.data_validation(cellname, {"validate": "list", "source": images})
workbook.close()
return newfile
@app.route() # 略
def download():
file_path = generate_excel_template()
return send_file(file_path, as_attachment=True)
注意:在该方法中,是将列表的内容直接放在了source里,xlsxwriter对此有所限制,内容长度不超过255,否则无法生效,该单元格最终会变成普通的单元格。
列表维护在单独sheet中
对于列表内容较长的场景,建议单独创建一个sheet,用于作为下拉列表的选项。上述代码可改造为:
def save_data_validate_options_sheet(workbook, object_options):
"""
保存数据校验的待选项到单独的sheet,object_options格式为{'flavor':[], 'image':[]}
"""
key2cellrange = {}
opt_sheet = workbook.add_worksheet("options")
col = 0
for k in object_options:
key2cellrange[k] = ""
opt_sheet.write(0, col, k)
row = 1
for opt in object_options[k]:
opt_sheet.write(row, col, opt)
row += 1
col_char = get_column(col)
cellrange = "='{sheet}'!${col}${row1}:${col}${row2}".format(sheet=sheetname,col=col_char, row1=2, row2=row)
key2cellrange[k] = cellrange
col += 1
return key2cellrange
def generate_excel_template():
newfile = os.path.join(current_app.config['TMP_DIR'], 'download', 'Template-%s.xlsx' % (time.strftime("%Y%m%d%H%M")))
workbook = xlsxwriter.Workbook(newfile)
sheet = workbook.add_worksheet("Sheet1")
row = 0
# 表头
for idx, header in enumerate(header_row):
sheet.write(row, idx, header)
row += 1
# 统一获取待选项
flavors = get_openstack_flavor_names() # todo,从数据库获取数据列表
images = get_openstack_image_names() #todo,从数据库获取数据列表
object_options = {"flavor": flavors, "image": images}
key2cellrange = save_data_validate_options_sheet(workbook, object_options)
# flavor
cellname = get_excel_cell_name(row, header_row.index("flavor"))
sheet.data_validation(cellname, {"validate": "list", "source": key2cellrange['flavor']})
# image
cellname = get_excel_cell_name(row, header_row.index("image"))
sheet.data_validation(cellname, {"validate": "list", "source": key2cellrange['image']})
workbook.close()
return newfile