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()方法,可規定大小),防止文件過大,占用大量內存。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM