Django-Model导出带验证的excel表单

Django-Model导出带验证的excel表单
Page content
class RetrieveModelMixin:
    """
    允许获得模板及对应key
    """
    def retrieve(self, request, *args, **kwargs):
        pk = request.parser_context["kwargs"]["pk"]
        model = self.serializer_class.Meta.model
        fields = model._meta.fields

        filter_item = lambda _: _.editable
        fields_maps = {item.name: item.verbose_name for item in fields if filter_item(item)}
        if pk == "__csv":  # 模板文件下载
            response = HttpResponse(content_type="text/csv")
            response["Content-Disposition"] = f'attachment; filename="{self.basename}_model.csv"'
            response.write(codecs.BOM_UTF8)
            writer = csv.writer(response)
            writer.writerow([item.verbose_name for item in fields if filter_item(item)])
            return response
        elif pk == "__xlsx": # 模板xlsx下载
            cell_req = NamedStyle(name='req',border=Border(top=Side(style='thick', color="00FF6600")))
            cell_blank = NamedStyle(name='blank',border=Border(top=Side(style='thick', color="0000FFFF")))
            cell_styles = []
            
            def add_valid(worksheet,item,col):
                """[设定认证器]]

                Args:
                    worksheet ([type]): [description]
                    item ([type]): [列]
                    col ([type]): [col字母]

                Raises:
                    ValueError: [description]
                """
                col = col.strip().upper()
                cell_styles.append(lambda : setattr(worksheet[f'{col}1'],'style',cell_blank if item.blank else  cell_req))
                dv = None
                if item.choices:
                    if not re.match('[A-z]*',col):
                        raise ValueError('invalid col')
                    dv = DataValidation(type="list", formula1=f'"{",".join([v.label for k, v in dict(item.choices).items() if v.label])}"', allow_blank=True)
                    dv.prompt = '请选择列表中的选项'
                    dv.promptTitle = 'List Selection'
                elif isinstance(item,models.IntegerField):
                    dv = DataValidation(type="whole")
                    dv.prompt = '请输入一个整数'
                    dv.promptTitle = 'Decimal'
                elif isinstance(item,(models.DateField,models.DateTimeField,)):
                    dv = DataValidation(type="date")
                    dv.prompt = '请输入一个日期或时间'
                    dv.promptTitle = 'DateTime'
                elif isinstance(item,models.TimeField):
                    dv = DataValidation(type="time")
                    dv.prompt = '请输入一个时间'
                    dv.promptTitle = 'Time'
                elif isinstance(item,models.GenericIPAddressField):
                    dv = DataValidation(type="custom",formula1='=AND((LEN(A2)-LEN(SUBSTITUTE(A2,".","")))=3,ISNUMBER(SUBSTITUTE(A2,".","")+0))')
                    dv.prompt = '请输入一个IP'
                    dv.promptTitle = 'IPaddress'
                if not dv:
                    return
                
                dv.promptTitle = item.__class__.__name__
                dv.error ='输入内容有误'
                dv.errorTitle = 'Invalid Entry'
                worksheet.add_data_validation(dv)
                dv.add(f'{col}1:{col}1048576')
                    
            def excel_column_name(n):
                """[数字转列名]

                Args:
                    n ([type]): [description]

                Returns:
                    [type]: [description]
                """
                name = ''
                while n > 0:
                    n, r = divmod (n - 1, 26)
                    name = chr(r + ord('A')) + name
                return name
                
            wb = Workbook()
            ws = wb.active
            header,body = [],[]
            keys = { item:num for num,item in enumerate(model._meta.export_order)}
            for num,item in  enumerate( filter(filter_item, sorted(fields ,key=lambda _: keys.get(_.name,1000)))) :
                header.append(item.verbose_name)
                add_valid(ws,item,excel_column_name(num+1))
            ws.append(header)
            [_() for _ in cell_styles]
            ws.append(body)
            response = HttpResponse(content=save_virtual_workbook(wb), content_type='application/ms-excel')
            response["Content-Disposition"] = f'attachment; filename="{self.basename}_model.xlsx"'
            return response
        elif pk == "__fields":  # 模板对应翻译
            return Response(
                {
                    "data": {item.verbose_name: item.name for item in fields if filter_item(item)},
                    "error": "",
                }
            )
        elif pk == "__export_all":  # 导出所有文档
            datas = [obj.to_dict() for obj in model.objects.all()]
            if datas:
                response = HttpResponse(content_type="text/csv")
                response["Content-Disposition"] = f'attachment; filename="{self.basename}_all.csv"'
                response.write(codecs.BOM_UTF8)
                writer = csv.writer(response)
                writer.writerow([fields_maps.get(key, key) for key in datas[0].keys()])
                for item in datas:
                    writer.writerow(list(item.values()))
                return response
            else:
                return Response(
                    {
                        "detail": "empty datas",
                    },
                    status=400,
                )
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

0x01 Excel中列名与序列的转换

def excel_column_name(n):
    """Number to Excel-style column name, e.g., 1 = A, 26 = Z, 27 = AA, 703 = AAA."""
    name = ''
    while n > 0:
        n, r = divmod (n - 1, 26)
        name = chr(r + ord('A')) + name
    return name

def excel_column_number(name):
    """Excel-style column name to number, e.g., A = 1, Z = 26, AA = 27, AAA = 703."""
    n = 0
    for c in name:
        n = n * 26 + 1 + ord(c) - ord('A')
    return n