一.项目需求

  批量上传图片,然后批量导入(使用excel)每个图片对应的属性(属性共十个,即对应十个字段,其中外键三个)。

二.问题

  一次可能上传成百上千张图片和对应字段,原来数据库的设计我将图片和对应属性放在一张表中,图片不可能和对应字段一起批量导入,如果先导入图片,其他字段必须允许为空,且在导入对应属性时,会遍历刚上传已经存在数据库中的图片,然后更新数据库,增加对应属性,这样会给服务器造成很大的压力,且很有可能出现错误(如图片对应不上,因此很多字段为空或只有图片,会使很多错误很难捕捉。)

三.实践中的解决方法

  1.分成两张表:

    我首先想到将图片和对应字段分开成两张表,先上传图片,然后在导入对应属性,然而仔细一想,问题似乎解决得不完善,导入使如何对应图片id,还是直接对应图片名,还有是否有可能图片已经保存到数据库,但是excel中没有该图片的信息,这也会浪费很多的空间,因此此方法还有待提高。

  2.使用缓存:

    然后我最后想到了缓存,也决定使用该方法批量上传与导入,思路大概是:上传图片先暂时存入缓存(我这里时图片名为键,图片临时文件对象为值),设置一定的时效,然后在上传excel判断excel的格式及列标题等,这些都对应时,然后将外键数据从数据库取出,一行一行判断excel中的数据的外键是否满足,以及图片是否在缓存中,如果条件都满足,然后这一行数据构成数据库中的一个Queryset对象存入列表,这样就将数据验证完毕,最后验证完所有的数据后,使用bulk_create()方法批量写入,或者可以使用get_or_create()方法批量导入(可以去重,但更耗时)。

    2.1图片和excel文件上传序列化如下:

 class RockImageSerializer(serializers.Serializer):
imgs = serializers.ListField(child=serializers.FileField(max_length=100,
), label="地质薄片图片",
help_text="地质薄片图片列表", write_only=True) def create(self, validated_data):
try:
imgs = validated_data.get('imgs')
notimg_file = []
for img in imgs:
img_name = str(img)
if not img_name.endswith(('.jpg', '.png', '.bmp', '.JPG', '.PNG', '.BMP')):
notimg_file.append(img_name)
else:
# 将图片加入缓存
cache.set(img_name, img, 60 * 60 * 24)
if notimg_file:
return {'code': -2, 'msg': '部分未上传成功,请检查是否为图片,失败文件部分如下:{0}'.format(','.join(notimg_file[:10]))}
return {'code': 1}
except Exception as e:
return {'code': -1} def validate_imgs(self, imgs):
if imgs:
return imgs
else:
raise serializers.ValidationError('缺失必要的字段或为空') class SourceSerializer(serializers.Serializer):
"""
批量上传序列化(excel)
"""
source = serializers.FileField(required=True, allow_empty_file=False,
error_messages={'empty': '未选择文件', 'required': '未选择文件'}, help_text="excel文件批量导入",
label="excel文件")

    2.2view视图如下:

 class ImageViewset(viewsets.GenericViewSet, mixins.CreateModelMixin, mixins.ListModelMixin):
parser_classes = (MultiPartParser, FileUploadParser,)
authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
serializer_class = RockImageSerializer
queryset = RockImage.objects.all() def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
success_status = serializer.is_valid()
if not success_status:
errors = serializer.errors
first_error = sorted(errors.items())[0]
return Response({'code': -1, 'msg': first_error[1]},
status=status.HTTP_400_BAD_REQUEST)
serializer_code = self.perform_create(serializer)
if serializer_code['code'] == 1:
headers = self.get_success_headers(serializer.data)
return Response({'code': 1, 'msg': '添加成功'}, status=status.HTTP_201_CREATED, headers=headers)
elif serializer_code['code'] == -2:
return Response({'code': -2, 'msg': serializer_code['msg']}, status=status.HTTP_400_BAD_REQUEST)
else:
return Response({'code': -2, 'msg': '图片上传过程中发生意外,请稍后重试'}, status=status.HTTP_400_BAD_REQUEST) def perform_create(self, serializer):
return serializer.save() class NewRockDetailViewset(viewsets.GenericViewSet, mixins.CreateModelMixin):
"""
批量上传字段接口
"""
authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
serializer_class = SourceSerializer # permission_classes =[SuperPermission] def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
success_status = serializer.is_valid()
if not success_status:
errors = serializer.errors
first_error = sorted(errors.items())[0]
return Response({'code': -1, 'msg': first_error[1]},
status=status.HTTP_400_BAD_REQUEST)
files = request.FILES.get('source')
if files.content_type == 'application/vnd.ms-excel' or files.content_type == 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
content = []
# 读取excel文件每次读取25M
for chunk in files.chunks():
content.append(chunk)
try:
ExcelFile = xlrd.open_workbook(filename=None, file_contents=b''.join(content),
encoding_override='gbk')
sheet = ExcelFile.sheet_by_index(0)
total_rows = sheet.nrows
head = sheet.row_values(0)
if total_rows <= 1:
return Response({'code': -3, 'msg': '数据为空或无列标题'})
if head[0] == "图片" and head[1] == "地区" and head[2] == "井号" and head[
3] == "年代地层" and head[
4] == "岩石地层" and head[5] == "偏光类型" and head[6] == "岩性" and head[
7] == "深度" and head[8] == "组分特征" and head[9] == "古生物特征" and head[
10] == "岩性特征" and head[11] == "孔缝特征":
address = Address.objects.filter(region_type=4).values_list('id', 'region')
polarizedtype = PolarizedType.objects.all().values_list('id', 'pol_type')
lithological = Lithological.objects.all().values_list('id', 'lit_des')
add_count = address.count()
pol_count = polarizedtype.count()
lit_count = lithological.count()
all_counts = [add_count, pol_count, lit_count]
add_ids = []
add_datas = []
pol_ids = []
pol_datas = []
lit_ids = []
lit_datas = []
max_num = max(all_counts)
for row in range(max_num):
if row < add_count:
add_ids.append(address[row][0])
add_datas.append(address[row][1])
if row < pol_count:
pol_ids.append(polarizedtype[row][0])
pol_datas.append(polarizedtype[row][1])
if row < lit_count:
lit_ids.append(lithological[row][0])
lit_datas.append(lithological[row][1])
err_data = []
r_data = []
r_sum = 0
for exc_row in range(1, total_rows):
row_value = sheet.row_values(exc_row)
img = cache.get(row_value[0], None)
add_value = row_value[4]
pol_value = row_value[5]
lit_value = row_value[6]
if img and add_value in add_datas and pol_value in pol_datas and lit_value in lit_datas and \
row_value[7]:
r_sum += 1
r_data.append(Rock(image=img, area_detail_id=int(add_ids[add_datas.index(add_value)]),
pol_type_id=int(pol_ids[pol_datas.index(pol_value)]),
lit_des_id=int(lit_ids[lit_datas.index(lit_value)]), depth=row_value[7],
lit_com=row_value[8], pal_fea=row_value[9], lit_fea=row_value[10],
por_fea=row_value[11]))
else:
err_data.append('第' + str(exc_row) + '行')
if r_sum:
Rock.objects.bulk_create(r_data)
if err_data:
return Response({'code': 1, 'msg': '共{0}条数据上传成功'.format(str(r_sum)),
'err_data': '共{0}条数据上传失败,部分错误数据如下:{1},请查看格式或图片是否不存在'.format(
str(len(err_data)), ','.join(err_data[:10]))},
status=status.HTTP_201_CREATED)
else:
return Response({'code': 0, 'msg': '共{0}条数据上传成功'.format(str(r_sum)), 'err_data': '共0条数据失败'},
status=status.HTTP_201_CREATED)
else:
return Response({'code': -3, 'msg': '共0条数据上传成功,请检查数据格式或图片未上传',
'err_data': '共{0}条数据上传失败'.format(str(len(err_data)))},
status=status.HTTP_400_BAD_REQUEST)
else:
return Response({'code': -2, 'msg': 'excel列标题格式错误'})
except Exception as e:
print(e)
return Response({'code': -1, 'msg': '无法打开文件'}, status=status.HTTP_400_BAD_REQUEST) else:
return Response({'code': -1, 'msg': '文件格式不正确'},
status=status.HTTP_400_BAD_REQUEST)

    这样便较好的解决了批量上传图片和对应字段的问题,注意:验证一定要较为全面,还有文件读写一定要分片读(可以利用chunks()方法,可规定大小),防止文件过大,占用大量内存。

django rest framework批量上传图片及导入字段的更多相关文章

  1. Django REST Framework批量更新rest_framework_extensions

    Django REST framework 是一套基于Django框架编写RESTful风格API的组件. 其中mixins配合viewsets能极其方便简化对数据的增删改查, 但本身并没有对数据的批 ...

  2. 3- vue django restful framework 打造生鲜超市 - model设计和资源导入

    3- vue django restful framework 打造生鲜超市 - model设计和资源导入 使用Python3.6与Django2.0.2(Django-rest-framework) ...

  3. Django REST framework+Vue 打造生鲜超市(三)

    四.xadmin后台管理 4.1.xadmin添加富文本插件 (1)xadmin/plugins文件夹下新建文件ueditor.py 代码如下: # xadmin/plugins/ueditor.py ...

  4. 深度解析Django REST Framework 批量操作

    我们都知道Django rest framework这个库,默认只支持批量查看,不支持批量更新(局部或整体)和批量删除. 下面我们来讨论这个问题,看看如何实现批量更新和删除操作. DRF基本情况 我们 ...

  5. [Django REST framework - 自动生成接口文档、分页]

    [Django REST framework - 自动生成接口文档.分页] 自动生成接口文档 # 后端人员写好接口,编写接口文档,给前端人员看,前端人员依照接口文档开发 # 公司里主流 -后端,使用w ...

  6. Django REST framework 中文教程1:序列化

    建立环境 在我们做任何事情之前,我们将使用virtualenv创建一个新的虚拟环境.这将确保我们的包配置与我们正在开展的任何其他项目保持良好的隔离. virtualenv envsource env/ ...

  7. Django REST framework+Vue 打造生鲜超市(一)

    一.项目介绍 1.1.掌握的技术 Vue + Django Rest Framework 前后端分离技术 彻底玩转restful api 开发流程 Django Rest Framework 的功能实 ...

  8. Django REST framework+Vue 打造生鲜超市(十二)

    十三.首页.商品数量.缓存和限速功能开发  13.1.轮播图接口实现 首先把pycharm环境改成本地的,vue中local_host也改成本地 (1)goods/serializer class B ...

  9. Django REST Framework API Guide 06

    本节大纲 1.Validators 2.Authentication Validators 在REST框架中处理验证的大多数时间,您将仅仅依赖于缺省字段验证,或在序列化器或字段类上编写显式验证方法.但 ...

随机推荐

  1. Python:Day20 模块

    模块是用来组织函数的. 模块一共3种: python标准库 第三方模块 应用程序自定义模块 模块搜索路径:sys.path import sys print(sys.path) import calc ...

  2. 转载 1-EasyNetQ介绍(黄亮翻译) https://www.cnblogs.com/HuangLiang/p/7105659.html

    EasyNetQ 是一个容易使用,坚固的,针对RabbitMQ的 .NET API. 假如你尽可能快的想去安装和运行RabbitMQ,请去看入门指南.EasyNetQ是为了提供一个尽可能简洁的适用与R ...

  3. Redis 实现安全队列

    Redis的列表数据结构可以让我们方便的实现消息队列 例如用 LPUSH(BLPUSH)把消息入队,用 RPOP(BRPOP)获取消息 绝大部分的情况下,这些操作都是没问题的,但并不能保证绝对安全 当 ...

  4. 理解koa2 之 async + await + promise

    koa是下一代的Node.js web框架. 我们首先使用koa来实现一个简单的hello world吧!假如目前的项目结构如下: ### 目录结构如下: koa-demo1 # 工程名 | |--- ...

  5. Android/Linux boot time分析优化

    如果需要优化boot time,就需要一个量化的工具来分析每个阶段的时间消耗.这种类型的优化特别适合使用基于timeline的图表,有着明显的时间顺序.要求不但能给出整个流程消耗的时间,还要能对流程进 ...

  6. .net core 简单项目的创建

    1.linux 安装net coref https://www.microsoft.com/net/learn/get-started/linuxubuntu 2.创建目录 2.创建控制台项目 第一次 ...

  7. Web组件流畅拖动效果

    拖动效果,可以形象的帮助用户处理一些问题,比如Windows删除文件,只需将文件拖动至回收站即可.比起右键显得更形象,我觉得更好玩一点^_^.当然,在其他许多方面,其实也有用到拖动效果,只是他们不是那 ...

  8. 开发框架模块视频系列(2)-Winform分页控件介绍

    在软件开发过程中,为了节省开发时间,提高开发效率,统一用户处理界面,尽可能使用成熟.功能强大的分页控件,这款Winform环境下的分页控件,集成了数据分页.内容提示.数据打印.数据导出.表头中文转义等 ...

  9. Java执行JavaScript脚本破解encodeInp()加密

    一:背景 在模拟登录某网站时遇到了用户名和密码被JS进行加密提交的问题,如图: 二:解决方法 1.我们首先需要获得该JS加密函数,一般如下: conwork.js var keyStr = " ...

  10. Java调度池的实现原理

    下图是关于ScheduledFutureTask的继承体系结构图.