Django丨REST framework中导入导出excel

文章目录

  • 前言
  • 1、文件流方式导出`excel`
    • 1.1 后端文件代码
      • 1.1.1 路由文件代码
      • 1.1.2 视图文件代码
    • 1.2 前端`vue`文件代码
  • 2、文件流方式导入`excel`
    • 2.1 后端文件代码
    • 2.2 前端`vue`文件代码
  • 3、总结


前言

采用文件流方式导入导出文件,不会在服务端产生file,节约服务端存储空间。


1、文件流方式导出excel

示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。

1.1 后端文件代码

1.1.1 路由文件代码

# WorkShopApi/urls.py   主路由
from django.conf import settings
from django.views.static import serve
from django.urls import path, include, re_path

urlpatterns = [
    path('api/backend/', include('apps.backend.urls')),
]
# apps/backend/urls.py   分路由
from apps.backend import views
from django.urls import path, include
from rest_framework import routers

router = routers.SimpleRouter()
router.register('user_auth', views.UserAuthView, basename='user_auth')

urlpatterns = [
    path('', include(router.urls)),
    path('regulations/download/', views.regulation_download, name='regulation_download'),
]

1.1.2 视图文件代码

# apps/backend/views.py
import io
import xlwt

from django.http import HttpResponse
from django.utils.encoding import escape_uri_path

def regulation_download(request, *args, **kwargs): 
    # 1.获取从前端传输过来的数据,并通过json序列化转成list格式
    excel_data_list = json.loads(request.body)

    # 2.创建一个工作簿(workbook),并设置编码
    workbook = xlwt.Workbook(encoding='utf-8')

    # 3.创建一个工作表(worksheet)
    worksheet = workbook.add_sheet("My Worksheet")
    
    # 3.1 设置列宽
    worksheet.col(0).width = 256 * 8    # 8个字符0的宽度
    worksheet.col(1).width = 256 * 12
    worksheet.col(2).width = 256 * 30
    worksheet.col(3).width = 256 * 30
    worksheet.col(4).width = 256 * 30
    worksheet.col(5).width = 256 * 20
    worksheet.col(6).width = 256 * 16

    # 3.2 设置表头行高
    style = xlwt.easyxf('font:height 360;')  # 18pt,类型小初的字号
    row = worksheet.row(0)
    row.set_style(style)

    # 4.写表头部分
    # 4.1 表头数据
    header_list = ['id', '法规类型', '法规名称', '法规网站地址', '主要内容', '实施日期', '备注']

    # 4.2 表头样式设置(可选)
    style = xlwt.XFStyle()  # 初始化样式
    font = xlwt.Font()  # 为样式创建字体
    font.name = "Times New Roman"
    font.bold = True  # 加粗
    font.underline = False  # 下划线
    font.italic = False  # 斜体字
    style.font = font  # 设定样式

    # 4.3 写表头
    for header_data in enumerate(header_list):
        worksheet.write(0, header_data[0], header_data[1], style)

    # 4.4 写表格内容数据
    for index, row_data in enumerate(excel_data_list):
        worksheet.write(index + 1, 0, row_data.get('id'))	# 第一行为表头
        worksheet.write(index + 1, 1, row_data.get('regulation_type_text'))
        worksheet.write(index + 1, 2, row_data.get('title'))
        worksheet.write(index + 1, 3, row_data.get('url'))
        worksheet.write(index + 1, 4, row_data.get('content_main'))
        worksheet.write(index + 1, 5, row_data.get('effective_date'))
        worksheet.write(index + 1, 6, row_data.get('meno'))
        
        # 表头行高
        row = worksheet.row(index + 1)
        row.set_style(style)

    # 5.保存文件
    # 5.1 保存到本地文件:保存文件目录级别在项目目录下,与apps同级
    # workbook.save("test.xls")

    # 5.2 保存文件流到内存
    sio = io.BytesIO()
    workbook.save(sio)
    sio.seek(0)
    
    # 5.3 返回数据给前端
    # 5.3.1 获取文件流,设置文件类型
    response = HttpResponse(sio.getvalue(), content_type='application/vnd.ms-excel')
    
    # 5.3.2 生成文件名称,名称包含法规导出+年月日时分秒数字的组合,避免重复
    file_name = '法规导出{}'.format(datetime.now().strftime(format('%Y%m%d%H%M%S')))
    
    # 5.3.3 配置Response Headers中`Content-Disposition`信息,支持中文
    response['Content-Disposition'] = "attachment;filename={}.xls".format(escape_uri_path(file_name))
    
    # 5.3.4 必须将`Content-Disposition`暴露给前端,否则前端获取不到信息
    response['Access-Control-Expose-Headers'] = "Content-Disposition"
    
	# 5.3.5 返回数据给前端
    response.write(sio.getvalue())
    return response
  • 后端参考文档:
    1、Content-Disposition中文件乱码不显示问题:https://www.cnblogs.com/hanfe1/p/12161231.html
    2、响应头Content-Disposition在浏览器中能看到但在axios的返回值response中没有显示且只有一个content-type: “application/octet-stream”问题:https://blog.csdn.net/qq_43617906/article/details/129369193
    3、django导入导出excel实践:https://www.cnblogs.com/xiugeng/p/10912417.html

1.2 前端vue文件代码

<template>
    <el-row>
            <el-table :data="state.regulationList" size="default" border style="width: fit-content" table-layout="auto"
                      show-overflow-tooltip @selection-change="handleSelectionChange">
                <el-table-column type="selection" width="55"/>
                    ……
				</el-table>
    </el-row>
    <el-row :justify="'end'" class="menus">
            <el-dropdown class="menu" trigger="click" @command="handleCommand">
                <el-button>更多操作
                    <el-icon class="el-icon--right">
                        <ArrowDown/>
                    </el-icon>
                </el-button>
                <template #dropdown>
                    <el-dropdown-menu>
                        <el-dropdown-item command="doBatchImport">
                            <el-upload
                                class="upload-demo"
                                :action="excelUploadUrl"
                                multiple
                                :limit="3"
                                accept=".xls"
                                :on-success="upLoadSuccess"
                            >
                                <span>批量导入</span>
                            </el-upload>
                        </el-dropdown-item>

						<!--核心代码-->
                        <el-dropdown-item command="doExportExcel">
                            导出EXCEL
                        </el-dropdown-item>
                    </el-dropdown-menu>
                </template>
            </el-dropdown>
            <el-button class="menu" @click="doBatchDelete" command="delete">批量删除</el-button>
        </el-row>
</template>

<script setup>
import {reactive, getCurrentInstance, onMounted, onBeforeUnmount} from "vue";
import {ElMessage} from "element-plus";
import {useStore} from "vuex";
import {saveAs} from 'file-saver'

const {proxy} = getCurrentInstance();

function handleSelectionChange(valueList) {
    state.selectList = valueList;
    // console.log(valueList)
}

function handleCommand(command) {
    // 1.将数据传到后台,生成文件流,return response
    if (command === 'doExportExcel') {
        
        // 1.发送post请求,携带数据到后端
        proxy.$axios.post(
            `/api/backend/regulations/download/`,
            state.selectList,	// 多选的数据列表
            {responseType: 'arraybuffer'} // 必须添加此选项,将responseType设为arraybuffer
        ).then((res) => {
            
            // 2.获取文件名称
            const contentDisposition = res.headers.get('content-disposition')
            const fileNameRegexp = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
            const matches = fileNameRegexp.exec(contentDisposition);
            const filename = (matches != null && matches[1] ? decodeURIComponent(matches[1].replace(/['"]/g, '')) : 'unknown');

            // 2.导出到本地保存
            const blob = new Blob([res.data], {type: 'application/vnd.ms-excel'});
            saveAs(blob, filename);
        })
    }
}
    
</script>
  • 前端参考文档:
    1、正则获取content-disposition中的文件名称:https://www.5axxw.com/questions/simple/bapmy1
    2、vue 根据后端获取的文件流生成文件 并下载:https://www.5axxw.com/questions/simple/n9he0j

2、文件流方式导入excel

2.1 后端文件代码

# apps/backend/views.py
class RegulationView(ModelViewSet):
    queryset = models.Regulations.objects.all()
    serializer_class = RegulationModelSerializer
    pagination_class = MyPage
    
    @action(detail=False, methods=['post'], url_path="upload")
    def upload(self, request):
        """ 上传excel文件并将数据写入到数据库中 """
        # 1.读取上传的文件
        # request.POST,request.GET,request.FILES
        upload_object = request.FILES.get('file')

        # 2.判断文件大小
        if upload_object.size > 2 * 1024 * 1024:    # 如果文件超过2M,提示:上传文件超过2M,太大了
            return Response({
                'code': return_code.ERROR,
                'msg': '上传文件超过2M,太大了'
            })

        # 3.获取文件类型的后缀名,如果不是xls,则提示:上传文件不是excel标准格式【.xls】文件,请重新上传
        file_type_name = upload_object.name.split('.')[1]
        if file_type_name != 'xls':
            return Response({
                'code': return_code.ERROR,
                'msg': '上传文件不是excel标准格式【.xls】文件,请重新上传'
            })

        # 4.获取文件数据,并存入数据库
        # 4.1 获取Excel文件读取器,加上utf-8编码可防止遇到中文字符乱码
        data = xlrd.open_workbook(file_contents=upload_object.read(), encoding_override='utf-8')

        # 4.2 通过索引获取第一个sheet工作簿
        table = data.sheet_by_index(0)
        nrows = table.nrows  # 总行数

        # 4.3 获取table数据列表,item为每行数据
        data_list = [table.row_values(i) for i in range(nrows)]

        # 4.4 返回前端的数据,即将要在前端state.dataList中添加的数据,在前端进行展示
        res_data_list = []
        regulation_type_class_map = {1: 'success', 2: '', 3: 'danger'}
        regulation_type_text_map = {1: '政府采购', 2: '招标投标', 3: '其他'}
        for index, item in enumerate(data_list):

            if table.cell(index, 5).ctype == 3:
                # 4.5 如果数据为日期格式,将数据格式转化为:2023年8月18日
                date_value = xlrd.xldate_as_tuple(table.cell(index, 5).value, data.datemode)
                effective_date = date(*date_value[:3]).strftime('%Y年%m月%d日')

                # 4.6 根据数据库对应字段获取每行数据,写入数据库中
                row_data_dict = {
                    'regulation_type': item[0],
                    'title': item[1],
                    'url': item[2],
                    'content_main': item[3],
                    'meno': item[4],
                    'effective_date': effective_date,
                }
                models.Regulations.objects.create(**row_data_dict)

                # 4.7 返回前端的数据字段增加regulation_type_class、regulation_type_text,用于前端渲染
                row_data_dict['regulation_type_class'] = regulation_type_class_map[item[0]]
                row_data_dict['regulation_type_text'] = regulation_type_text_map[item[0]]
                res_data_list.append(row_data_dict)

        return Response({
            'code': return_code.SUCCESS,
            'data': res_data_list,
            'msg': '批量导入数据成功'
        })

2.2 前端vue文件代码

<template>
    <el-row>
            <el-table :data="state.regulationList" size="default" border style="width: fit-content" table-layout="auto"
                      show-overflow-tooltip @selection-change="handleSelectionChange">
                <el-table-column type="selection" width="55"/>
                    ……
				</el-table>
    </el-row>
    <el-row :justify="'end'" class="menus">
            <el-dropdown class="menu" trigger="click" @command="handleCommand">
                <el-button>更多操作
                    <el-icon class="el-icon--right">
                        <ArrowDown/>
                    </el-icon>
                </el-button>
                <template #dropdown>
                    <el-dropdown-menu>
                        <el-dropdown-item command="doBatchImport">
                            <!--核心代码 accept:文件格式,action:上传路由-->
                            <el-upload
                                class="upload-demo"
                                :action="excelUploadUrl"
                                multiple
                                :limit="3"
                                accept=".xls"								
                                :on-success="upLoadSuccess"
                            >
                                <span>批量导入</span>
                            </el-upload>
                        </el-dropdown-item>

                        <el-dropdown-item command="doExportExcel">
                            导出EXCEL
                        </el-dropdown-item>
                    </el-dropdown-menu>
                </template>
            </el-dropdown>
            <el-button class="menu" @click="doBatchDelete" command="delete">批量删除</el-button>
        </el-row>
</template>

<script setup>
    
  function upLoadSuccess(res) {
    // console.log(res)
    if (res.code === 0) {
		// 循环后端返回数据,逐条赋值到state.regulationList
        res.data.data.forEach(function (value, index) {
            state.regulationList.push(value)
        })
        ElMessage.success('批量导入成功')
    } else {
        ElMessage.error('批量导入失败')
    }
}  
</script>

3、总结

本文简要介绍了django restframework中文件流方式导入导出excel文件的方法。

你可能感兴趣的:(Python之路,django,excel,sqlite)