feat: sheet
This commit is contained in:
30
nc_http/tests/sheet.py
Normal file
30
nc_http/tests/sheet.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import unittest
|
||||
|
||||
from nc_http.utils.sheet.sample import SampleSheet
|
||||
|
||||
|
||||
class SheetTestCase(unittest.TestCase):
|
||||
test_data = [
|
||||
{
|
||||
'admin_unit_official_name': '测试地区 1',
|
||||
'count_valid_situation': 34,
|
||||
'count_valid_situation_per': 0.32,
|
||||
'count_valid_situation_yoy': 0.34,
|
||||
'count_valid_situation_mom': 0.88,
|
||||
},
|
||||
{
|
||||
'admin_unit_official_name': '测试地区 2',
|
||||
'count_valid_situation': 56,
|
||||
'count_valid_situation_per': 0.62,
|
||||
'count_valid_situation_yoy': 0.87,
|
||||
'count_valid_situation_mom': 0.42,
|
||||
},
|
||||
]
|
||||
|
||||
def test_create(self):
|
||||
sheet = SampleSheet(self.test_data, year='2018')
|
||||
sheet.create('sample.xlsx')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
0
nc_http/utils/sheet/__init__.py
Normal file
0
nc_http/utils/sheet/__init__.py
Normal file
6
nc_http/utils/sheet/paper_size.py
Normal file
6
nc_http/utils/sheet/paper_size.py
Normal file
@@ -0,0 +1,6 @@
|
||||
class PaperSize:
|
||||
A3 = 8
|
||||
A4 = 9
|
||||
A5 = 11
|
||||
B4 = 12
|
||||
B5 = 13
|
87
nc_http/utils/sheet/sample.py
Normal file
87
nc_http/utils/sheet/sample.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from nc_http.utils.sheet.sheet import Sheet
|
||||
|
||||
|
||||
class SampleSheet(Sheet):
|
||||
"""
|
||||
样例表格
|
||||
"""
|
||||
|
||||
all_fields = [
|
||||
'admin_unit_official_name',
|
||||
'count_valid_situation',
|
||||
'count_valid_situation_per',
|
||||
'count_valid_situation_yoy',
|
||||
'count_valid_situation_mom',
|
||||
]
|
||||
style_formats = {
|
||||
'title': {
|
||||
'font': 'Arial Unicode MS', 'font_size': 20, 'bold': False,
|
||||
'align': 'center', 'valign': 'vcenter',
|
||||
},
|
||||
'header': {
|
||||
'font': 'Arial Unicode MS', 'font_size': 11, 'bold': False,
|
||||
'align': 'center', 'valign': 'vcenter', 'border': True, 'text_wrap': False,
|
||||
},
|
||||
'body': {
|
||||
'font': 'Arial Unicode MS', 'font_size': 11, 'bold': False,
|
||||
'align': 'center', 'valign': 'vcenter', 'border': True, 'text_wrap': True,
|
||||
},
|
||||
'remark': {
|
||||
'font': 'Arial Unicode MS', 'font_size': 10, 'bold': False,
|
||||
'align': 'center', 'valign': 'vcenter',
|
||||
'bottom': 1, 'top_color': 'white', 'top': 1
|
||||
},
|
||||
'remark_year': {
|
||||
'font': 'Arial Unicode MS', 'font_size': 8, 'bold': False,
|
||||
'align': 'center', 'valign': 'vcenter',
|
||||
'bottom': 1, 'top_color': 'white', 'top': 1,
|
||||
'num_format': 'yyyy"年"'
|
||||
},
|
||||
}
|
||||
round_digits = 4
|
||||
row_stretches = [
|
||||
[0, 30],
|
||||
]
|
||||
col_stretches = [
|
||||
[0, 0, 29, {'text_wrap': True}],
|
||||
[1, 1, 20],
|
||||
[2, 2, 20, {'num_format': '0.00%'}],
|
||||
[3, 3, 20, {'num_format': '0.00%'}],
|
||||
[4, 4, 20, {'num_format': '0.00%'}],
|
||||
]
|
||||
|
||||
def set_sheet(self):
|
||||
self.title = '全市管辖单位样例数据统计表'
|
||||
self.headers = [
|
||||
['', '', '', ],
|
||||
['', '', '', ],
|
||||
['地区项目', '总数', '占比情况', '同比情况', '环比情况', ],
|
||||
]
|
||||
self.merges = [
|
||||
[0, 0, 0, 4, self.title, self.style_formats['title']],
|
||||
[1, 0, 1, 4, '{} 年'.format(self.meta['year']), self.style_formats['remark']],
|
||||
]
|
||||
|
||||
def clean_data(self):
|
||||
return [self.flat_row(item) for item in self.origin_data]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_data = [
|
||||
{
|
||||
'admin_unit_official_name': '测试地区 1',
|
||||
'count_valid_situation': 34,
|
||||
'count_valid_situation_per': 0.32,
|
||||
'count_valid_situation_yoy': 0.34,
|
||||
'count_valid_situation_mom': 0.88,
|
||||
},
|
||||
{
|
||||
'admin_unit_official_name': '测试地区 2',
|
||||
'count_valid_situation': 56,
|
||||
'count_valid_situation_per': 0.62,
|
||||
'count_valid_situation_yoy': 0.87,
|
||||
'count_valid_situation_mom': 0.42,
|
||||
},
|
||||
]
|
||||
sheet = SampleSheet(test_data, year='2018')
|
||||
sheet.create('sample.xlsx')
|
155
nc_http/utils/sheet/sheet.py
Normal file
155
nc_http/utils/sheet/sheet.py
Normal file
@@ -0,0 +1,155 @@
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from nc_http.core.excel.excel_writer import ExcelWriter
|
||||
from nc_http.utils.sheet.paper_size import PaperSize
|
||||
|
||||
|
||||
class Sheet:
|
||||
origin_data = [] # 源统计数据
|
||||
data = [] # 处理后的统计数据
|
||||
year = '' # 表数据代表年份
|
||||
title = '' # 表标题
|
||||
headers = [] # 表头指定
|
||||
merges = [] # 单元格合并指定
|
||||
row_stretches = [] # 行高指定
|
||||
col_stretches = [] # 列宽指定
|
||||
filename = '' # 文件名
|
||||
pdf_filename = '' # 文件名
|
||||
paper = PaperSize.A3 # 纸张大小
|
||||
|
||||
round_digits = 1 # 小数保留位数
|
||||
subtotal_fields = [] # 需要进行小计累加的字段
|
||||
all_fields = [] # 所有字段
|
||||
|
||||
style_formats = {} # 表头、表体样式
|
||||
|
||||
def __init__(self, data, title=None, headers=None, merges=None, row_stretches=None, col_stretches=None, **meta):
|
||||
self.meta = {}
|
||||
self.origin_data = data
|
||||
if title:
|
||||
self.title = title
|
||||
if headers:
|
||||
self.headers = headers
|
||||
if merges:
|
||||
self.merges = merges
|
||||
if row_stretches:
|
||||
self.row_stretches = row_stretches
|
||||
if col_stretches:
|
||||
self.col_stretches = col_stretches
|
||||
if meta:
|
||||
self.meta = meta
|
||||
|
||||
self.set_sheet()
|
||||
self.data = self.clean_data()
|
||||
# self.filename = self.create_filename()
|
||||
# self.pdf_filename = self.create_filename(suffix='pdf')
|
||||
|
||||
def set_sheet(self):
|
||||
"""
|
||||
处理相关动态配置
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
def clean_data(self):
|
||||
"""
|
||||
数据预清洗
|
||||
:return:
|
||||
"""
|
||||
return list(self.data)
|
||||
|
||||
def create(self, file_path):
|
||||
"""
|
||||
创建 excel 文件
|
||||
:param file_path:
|
||||
:return:
|
||||
"""
|
||||
return ExcelWriter.create_excel(
|
||||
file_path, self.data,
|
||||
headers=self.headers,
|
||||
merges=self.merges,
|
||||
row_stretches=self.row_stretches,
|
||||
col_stretches=self.col_stretches,
|
||||
paper=self.paper,
|
||||
style_formats=self.style_formats,
|
||||
)
|
||||
|
||||
def pack(self):
|
||||
"""
|
||||
创建 excel 文件句柄
|
||||
:return:
|
||||
"""
|
||||
return ExcelWriter.pack_excel(
|
||||
self.data,
|
||||
headers=self.headers,
|
||||
merges=self.merges,
|
||||
row_stretches=self.row_stretches,
|
||||
col_stretches=self.col_stretches,
|
||||
paper=self.paper,
|
||||
style_formats=self.style_formats,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _init_subtotal(cls, subtotal, fields=None):
|
||||
"""
|
||||
小计初始化
|
||||
:param subtotal:
|
||||
:param fields:
|
||||
:return:
|
||||
"""
|
||||
fields = fields or cls.subtotal_fields
|
||||
for field in fields:
|
||||
if subtotal.get(field) is None:
|
||||
subtotal[field] = 0
|
||||
|
||||
@classmethod
|
||||
def _add_subtotal(cls, subtotal, item, fields):
|
||||
"""
|
||||
小计累加
|
||||
:param subtotal: dict 小计
|
||||
:param item: dict 统计项
|
||||
:param fields: list 需要小计字段
|
||||
:return:
|
||||
"""
|
||||
for field in fields:
|
||||
if subtotal.get(field) is None:
|
||||
subtotal[field] = 0
|
||||
# subtotal.setdefault(field, 0)
|
||||
subtotal[field] += round((item[field] or 0), cls.round_digits)
|
||||
# 小计小数点后三位舍入(四舍六入五成双) 若遇到数据舍入错误建议牺牲性能更换为 Decimal 运算
|
||||
# subtotal[field] = round(subtotal[field], cls.round_digits)
|
||||
return subtotal
|
||||
|
||||
def create_filename(self, suffix='xlsx'):
|
||||
"""
|
||||
创建表格文件名
|
||||
:param suffix:
|
||||
:return:
|
||||
"""
|
||||
filename = '{}.{}'.format(self.title, suffix)
|
||||
return os.sep.join([self.meta['year'], '表', filename])
|
||||
|
||||
def flat_row(self, item):
|
||||
return [round(item[k], self.round_digits) if isinstance(item.get(k), float) else item.get(k, '') for k in
|
||||
self.all_fields]
|
||||
|
||||
@staticmethod
|
||||
def create_pdf(sheet_file, path=None):
|
||||
"""
|
||||
创建 PDF 文件 (依赖 soffice)
|
||||
:param sheet_file:
|
||||
:param path:
|
||||
:return:
|
||||
"""
|
||||
path = path or os.sep.join(sheet_file.split(os.sep)[:-1])
|
||||
command = "soffice --headless --convert-to pdf {} --outdir {}".format(sheet_file, path)
|
||||
try:
|
||||
p = subprocess.call(command, shell=True)
|
||||
print(p)
|
||||
assert p == 0, '转格式失败...'
|
||||
except Exception as e:
|
||||
print('create_pdf_file | command: {}'.format(command))
|
||||
# Logger.warning('create_pdf_file | command: {}'.format(command))
|
||||
raise e
|
||||
return sheet_file.replace('.xlsx', '.pdf')
|
Reference in New Issue
Block a user