django restframework 导出excel内容,可以查看另外一篇文章
一、基础环境
- web架构:前后端分离,前端使用vue,后端使用django 的rest framework
- django版本3.2
- django-excel 版本0.0.10
- djangorestframework版本3.12.4
二、需求
- 界面导入excel并保存到数据库
- 导入model包含外键类型
- 导入excel表格列顺序任意,即不需要强制按照excel导出顺序
#models.py
from django.db import models
from django.utils import timezone
class MyITtype(models.Model):
name = models.CharField(verbose_name="名称", max_length=128, unique=True)
ittype = models.SmallIntegerField(verbose_name="类型")
comment = models.TextField(verbose_name="备注", blank=True, default="")
def __str__(self):
return self.name
class meta:
db_table = "MyITtype"
verbose_name = "it资产类型"
class MyAsset(models.Model):
ittype = models.ForeignKey(MyITtype, on_delete=models.SET_NULL, verbose_name="产品类型", null=True)
code = models.CharField(verbose_name="资产编码", max_length=128, unique=True)
buytime = models.DateField(verbose_name="入仓时间", default=timezone.now)
usetime = models.DateField(verbose_name="分配时间", default=timezone.now)
comment = models.TextField(verbose_name="规格说明", blank=True, default="")
user = models.CharField(verbose_name="使用人", max_length=128, blank=True, default="")
status = models.IntegerField(verbose_name="状态")
def __str__(self):
return self.code
class meta:
db_table = "opGTITAsset"
verbose_name = "it固产"
三、功能实现
- 不使用序列化类:需要手动实现外键值转换为外键对应类对象
- 使用序化类:本文优选这种方法
实现细节
- django配置文件中加入文件上传后处理方法
- 为了通用性,特意封装了一个excel导入类
- 为了兼容导入保存到数据库(save_excel_to_db)和不保存到数据库(deal_excel),特意拆分为多个步骤
- 为了防止导入时根据主键覆盖指定内容,特意删除了id列内容,id为model默认主键
四、源码
#settings.py加入以下内容
#处理上传excel内容
FILE_UPLOAD_HANDLERS = [
"django_excel.ExcelMemoryFileUploadHandler",
"django_excel.TemporaryExcelFileUploadHandler"
]
#asset_load.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from .models import MyAsset
class ITAssetSer(ModelSerializer):
class meta:
model = MyAsset
fields = '__all__'
class GTLoadExcel():
"""
django 处理excel数据导入
将excel内容写入数据库时,需要注意以下两点
1、默认唯一值是name,如果你的model不是时,需要重写get_instance方法
2、默认删除id列内容,如果还有特殊处理,需要重写deal_special_field方法
deal_special_field重新注意点
a、需要同时删除id列时,加入父类id处理或者调用父类+自定义内容
b、不需要删除id列时,完全重写
"""
def __init__(self, ser_class, class_name, sheet):
"""
初始化
:param ser_class: model序化类
:param class_name: 待操作model类名称
:param sheet: 导入excel内容
"""
self.ser_class = ser_class
self.class_name = class_name
self.sheet = sheet
def save_excel_to_db(self):
self.deal_excel()
self.save_data()
def deal_excel(self):
"""
处理输入的excel并返回其内容列表
:return: [{},{}]
"""
self.data_list = self.deal_row_data()
return self.data_list
def save_data(self):
# 保存到数据库
for data in self.data_list:
self.in_obj_data = data
self.deal_special_field()
obj = self.get_instance()
if obj:
# update
ser = self.ser_class(obj, data=data)
else:
# create
ser = self.ser_class(data=data)
if ser.is_valid():
ser.save()
else:
logger.info(ser.errors)
raise
def deal_special_field(self):
"""
处理特殊 字段内容,如删除excel中主键内容,如id,防止输入主键内容意外覆盖正常内容
:param data:
:return:
"""
master_key = "id" # 删除主键列内容
del self.in_obj_data[master_key]
def get_instance(self):
"""
根据唯一字段获取对应对象
:return:
"""
obj = None
try:
obj = self.class_name.objects.get(name=self.in_obj_data["name"])
except:
pass
return obj
def deal_row_data(self):
"""
处理导入excel中每一个行内容
:return:
"""
sheet = self.sheet
header = sheet.row[0]
attr_list = self.get_attr_map(header)
post_data_list = []
for idx in range(1, sheet.number_of_rows()):
rdata = sheet.row[idx]
tmp = dict(zip(attr_list, rdata))
post_data_list.append(tmp)
return post_data_list
def get_attr_map(self, header):
"""
建立表头和model字段对应关系
:param header:
:return:
"""
field_dict = {field.verbose_name: field.name for field in self.class_name._meta.fields}
attr_list = []
for idx, head in enumerate(header):
attr_list.append(field_dict[head])
return attr_list
class ITAssetLoad(APIView):
use_model = MyAsset
queryset = MyAsset.objects.all()
serializer_class = ITAssetSer
def post(self, request, *args, **kwargs):
fobj = request.FILES["file"]
sheet = fobj.get_sheet()
instance = ITAssetExcelSpecial(ser_class=self.serializer_class, class_name=self.use_model, sheet=sheet)
instance.save_excel_to_db()
return Response({})
def put(self, request, *args, **kwargs):
self.http_method_not_allowed(request, *args, **kwargs)
class ITAssetExcelSpecial(GTLoadExcel):
def get_instance(self):
obj = None
try:
obj = self.class_name.objects.get(code=self.in_obj_data["code"])
except:
pass
return obj
def deal_special_field(self):
"""
删除id所在列
特殊处理时间字段
:param data:
:return:
"""
data = self.in_obj_data
# 删除id列
master_key = "id" # 删除主键列内容,默认防止导入时意外覆盖主键列内容
del data[master_key]
#excel中时间默认被转换为datetime类型,但是需要date类型,因此需要转换一下
data["buytime"] = data["buytime"].date()
data["usetime"] = data["usetime"].date()