• django rest framework批量上传图片及导入字段


    一.项目需求

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

    二.问题

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

    三.实践中的解决方法

      1.分成两张表:

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

      2.使用缓存:

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

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

     1 class RockImageSerializer(serializers.Serializer):
     2     imgs = serializers.ListField(child=serializers.FileField(max_length=100,
     3                                                              ), label="地质薄片图片",
     4                                  help_text="地质薄片图片列表", write_only=True)
     5 
     6     def create(self, validated_data):
     7         try:
     8             imgs = validated_data.get('imgs')
     9             notimg_file = []
    10             for img in imgs:
    11                 img_name = str(img)
    12                 if not img_name.endswith(('.jpg', '.png', '.bmp', '.JPG', '.PNG', '.BMP')):
    13                     notimg_file.append(img_name)
    14                 else:
    15                     # 将图片加入缓存
    16                     cache.set(img_name, img, 60 * 60 * 24)
    17             if notimg_file:
    18                 return {'code': -2, 'msg': '部分未上传成功,请检查是否为图片,失败文件部分如下:{0}'.format(','.join(notimg_file[:10]))}
    19             return {'code': 1}
    20         except Exception as e:
    21             return {'code': -1}
    22 
    23     def validate_imgs(self, imgs):
    24         if imgs:
    25             return imgs
    26         else:
    27             raise serializers.ValidationError('缺失必要的字段或为空')
    28 
    29 
    30 class SourceSerializer(serializers.Serializer):
    31     """
    32     批量上传序列化(excel)
    33     """
    34     source = serializers.FileField(required=True, allow_empty_file=False,
    35                                    error_messages={'empty': '未选择文件', 'required': '未选择文件'}, help_text="excel文件批量导入",
    36                                    label="excel文件")
    View Code

        2.2view视图如下:

      1 class ImageViewset(viewsets.GenericViewSet, mixins.CreateModelMixin, mixins.ListModelMixin):
      2     parser_classes = (MultiPartParser, FileUploadParser,)
      3     authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
      4     serializer_class = RockImageSerializer
      5     queryset = RockImage.objects.all()
      6 
      7     def create(self, request, *args, **kwargs):
      8         serializer = self.get_serializer(data=request.data)
      9         success_status = serializer.is_valid()
     10         if not success_status:
     11             errors = serializer.errors
     12             first_error = sorted(errors.items())[0]
     13             return Response({'code': -1, 'msg': first_error[1]},
     14                             status=status.HTTP_400_BAD_REQUEST)
     15         serializer_code = self.perform_create(serializer)
     16         if serializer_code['code'] == 1:
     17             headers = self.get_success_headers(serializer.data)
     18             return Response({'code': 1, 'msg': '添加成功'}, status=status.HTTP_201_CREATED, headers=headers)
     19         elif serializer_code['code'] == -2:
     20             return Response({'code': -2, 'msg': serializer_code['msg']}, status=status.HTTP_400_BAD_REQUEST)
     21         else:
     22             return Response({'code': -2, 'msg': '图片上传过程中发生意外,请稍后重试'}, status=status.HTTP_400_BAD_REQUEST)
     23 
     24     def perform_create(self, serializer):
     25         return serializer.save()
     26 
     27 
     28 class NewRockDetailViewset(viewsets.GenericViewSet, mixins.CreateModelMixin):
     29     """
     30     批量上传字段接口
     31     """
     32     authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
     33     serializer_class = SourceSerializer
     34 
     35     # permission_classes =[SuperPermission]
     36 
     37     def create(self, request, *args, **kwargs):
     38         serializer = self.get_serializer(data=request.data)
     39         success_status = serializer.is_valid()
     40         if not success_status:
     41             errors = serializer.errors
     42             first_error = sorted(errors.items())[0]
     43             return Response({'code': -1, 'msg': first_error[1]},
     44                             status=status.HTTP_400_BAD_REQUEST)
     45         files = request.FILES.get('source')
     46         if files.content_type == 'application/vnd.ms-excel' or files.content_type == 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
     47             content = []
     48             # 读取excel文件每次读取25M
     49             for chunk in files.chunks():
     50                 content.append(chunk)
     51             try:
     52                 ExcelFile = xlrd.open_workbook(filename=None, file_contents=b''.join(content),
     53                                                encoding_override='gbk')
     54                 sheet = ExcelFile.sheet_by_index(0)
     55                 total_rows = sheet.nrows
     56                 head = sheet.row_values(0)
     57                 if total_rows <= 1:
     58                     return Response({'code': -3, 'msg': '数据为空或无列标题'})
     59                 if head[0] == "图片" and head[1] == "地区" and head[2] == "井号" and head[
     60                     3] == "年代地层" and head[
     61                     4] == "岩石地层" and head[5] == "偏光类型" and head[6] == "岩性" and head[
     62                     7] == "深度" and head[8] == "组分特征" and head[9] == "古生物特征" and head[
     63                     10] == "岩性特征" and head[11] == "孔缝特征":
     64                     address = Address.objects.filter(region_type=4).values_list('id', 'region')
     65                     polarizedtype = PolarizedType.objects.all().values_list('id', 'pol_type')
     66                     lithological = Lithological.objects.all().values_list('id', 'lit_des')
     67                     add_count = address.count()
     68                     pol_count = polarizedtype.count()
     69                     lit_count = lithological.count()
     70                     all_counts = [add_count, pol_count, lit_count]
     71                     add_ids = []
     72                     add_datas = []
     73                     pol_ids = []
     74                     pol_datas = []
     75                     lit_ids = []
     76                     lit_datas = []
     77                     max_num = max(all_counts)
     78                     for row in range(max_num):
     79                         if row < add_count:
     80                             add_ids.append(address[row][0])
     81                             add_datas.append(address[row][1])
     82                         if row < pol_count:
     83                             pol_ids.append(polarizedtype[row][0])
     84                             pol_datas.append(polarizedtype[row][1])
     85                         if row < lit_count:
     86                             lit_ids.append(lithological[row][0])
     87                             lit_datas.append(lithological[row][1])
     88                     err_data = []
     89                     r_data = []
     90                     r_sum = 0
     91                     for exc_row in range(1, total_rows):
     92                         row_value = sheet.row_values(exc_row)
     93                         img = cache.get(row_value[0], None)
     94                         add_value = row_value[4]
     95                         pol_value = row_value[5]
     96                         lit_value = row_value[6]
     97                         if img and add_value in add_datas and pol_value in pol_datas and lit_value in lit_datas and 
     98                                 row_value[7]:
     99                             r_sum += 1
    100                             r_data.append(Rock(image=img, area_detail_id=int(add_ids[add_datas.index(add_value)]),
    101                                                pol_type_id=int(pol_ids[pol_datas.index(pol_value)]),
    102                                                lit_des_id=int(lit_ids[lit_datas.index(lit_value)]), depth=row_value[7],
    103                                                lit_com=row_value[8], pal_fea=row_value[9], lit_fea=row_value[10],
    104                                                por_fea=row_value[11]))
    105                         else:
    106                             err_data.append('' + str(exc_row) + '')
    107                     if r_sum:
    108                         Rock.objects.bulk_create(r_data)
    109                         if err_data:
    110                             return Response({'code': 1, 'msg': '共{0}条数据上传成功'.format(str(r_sum)),
    111                                              'err_data': '共{0}条数据上传失败,部分错误数据如下:{1},请查看格式或图片是否不存在'.format(
    112                                                  str(len(err_data)), ','.join(err_data[:10]))},
    113                                             status=status.HTTP_201_CREATED)
    114                         else:
    115                             return Response({'code': 0, 'msg': '共{0}条数据上传成功'.format(str(r_sum)), 'err_data': '共0条数据失败'},
    116                                             status=status.HTTP_201_CREATED)
    117                     else:
    118                         return Response({'code': -3, 'msg': '共0条数据上传成功,请检查数据格式或图片未上传',
    119                                          'err_data': '共{0}条数据上传失败'.format(str(len(err_data)))},
    120                                         status=status.HTTP_400_BAD_REQUEST)
    121                 else:
    122                     return Response({'code': -2, 'msg': 'excel列标题格式错误'})
    123             except Exception as e:
    124                 print(e)
    125                 return Response({'code': -1, 'msg': '无法打开文件'}, status=status.HTTP_400_BAD_REQUEST)
    126 
    127         else:
    128             return Response({'code': -1, 'msg': '文件格式不正确'},
    129                             status=status.HTTP_400_BAD_REQUEST)
    View Code

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

     

  • 相关阅读:
    python常用代码: logging 和 无视locale强制东8区
    近期文摘2
    偶尔用一次windows开发,有点明白为何人觉得macos对开发友好
    zz golang str json map互转
    golang一些有价值文章摘录
    从 'golang新手容易犯的三个错误' 中学到的知识点
    macos下使用postgresql
    python netsnmp与pysnmp
    快速实现python抓包嗅探
    macos下npm install 报错: fsevents .... permission denined
  • 原文地址:https://www.cnblogs.com/lyq-biu/p/10284632.html
Copyright © 2020-2023  润新知