Djangoでformを使用したファイルのアップロード時にMultiValueDictKeyErrorが発生する

実現したいこと

前提

djangoを使用して通販サイトの商品管理を行なっています。
商品を追加するための登録画面を、vue.jsを利用して作成しています。
同画面から新たに商品画像もアップロードしたく、登録項目を追加しましたが、
登録実行時に後述のエラーが発生し、画像のアップロードができない状態です。

vue,djangoともに経験が浅いため、設定に誤りがあればご指摘ください。
また他の手法でも構いませんので、実現できる方法があればご教授いただきたいです。

発生している問題・エラーメッセージ

MultiValueDictKeyError('image')

requestの中身

request.data: {'product': {'id': 1}, 'name': 'テスト', 'image': {}} request.FILES: <MultiValueDict: {}>

該当のソースコード

実際には他にも入力項目がありますが割愛しています。

vue.js

1<template> 2 <div class="customInternal"> 3 <div class="createCustomProductInternal"> 4 <div class="sectionContent"> 5 <form @submit.prevent="createCustomProduct" enctype="multipart/form-data"> 6 <div class="row"> 7 <div class="rowTitle">商品カテゴリ</div> 8 <div class="rowContent"> 9 <div class="formInput"> 10 <select id="category" name="category" v-model="category.id"> 11 <option class="placeholder" :value="null" disabled>選択してください</option> 12 <option v-for="cat in state.category_options" :value="cat.id">{{ cat.name }}</option> 13 </select> 14 </div> 15 </div> 16 </div> 17 18 <div class="row"> 19 <div class="rowTitle">表示名</div> 20 <div class="rowContent"> 21 <div class="formInput"> 22 <input id="name" v-model="options.name" type="text" name="name" required/> 23 </div> 24 </div> 25 </div> 26 27 <div class="row"> 28 <div class="rowTitle">商品画像</div> 29 <div class="rowContent"> 30 <div class="formInput"> 31 <label class="form-btn file-label"> 32 画像選択 33 <input id="product_image" ref="product_image" type="file" name="product_image" accept="image/jpeg, image/png" @change="onChamgeProductImage" required/> 34 </label> 35 <span v-html="product_image.name" /> 36 <div class="formImageWrapper" v-if="product_image.url"> 37 <img :src="product_image.url"> 38 </div> 39 </div> 40 </div> 41 </div> 42 43 <div class="row"> 44 <div class="rowTitle"></div> 45 <div class="rowContent"> 46 <iframe class="js-data-preview" src="" width="1000" height="600"></iframe> 47 </div> 48 </div> 49 50 <div class="row submitRow"> 51 <button class="form-btn" type="submit"> 52 作成 53 </button> 54 </div> 55 </form> 56 </div> 57 </div> 58 </div> 59</template> 60 61<script> 62import StoreMixin from "../../../mixins/StoreMixin" 63export default { 64 name: "CreateCustomProductForm", 65 mixins: [StoreMixin], 66 components: {}, 67 data: function (){ 68 return { 69 errors: [], 70 category: { 71 id: null 72 }, 73 options: { 74 name: null, 75 }, 76 product_image: { 77 name: null, 78 file: null, 79 url: null, 80 }, 81 } 82 }, 83methods: { 84 onChamgeProductImage: function() { 85 const file = this.$refs.product_image.files[0] 86 console.log(file); 87 if (file) { 88 $('#product_image').removeAttr('required') 89 this.product_image.name = file.name 90 this.product_image.file = file 91 console.log(this.product_image.file); 92 this.product_image.url = URL.createObjectURL(file) 93 } 94 }, 95 createCustomProduct: function() { 96 const formValue = { 97 product: this.category, 98 name: this.options.name, 99 image: this.product_image.file, 100 } 101 console.log(formValue.image); 102 this.store.addCustomProduct(formValue) 103 }, 104 }, 105 computed: { 106 }, 107 mounted() { 108 $('#user input').attr('required', true) 109 $('#category input').attr('required', true) 110 $('.js-data-preview').css({display: 'none'}) 111 } 112} 113</script> 114 115<style scoped> 116</style> 117

models.py

1def get_mydesign_image_path(instance, filename): 2 prefix = 'accounts/' + str(instance.user.id) + '/mydesign/' 3 return prefix + filename 4 5class MyDesign(models.Model): 6 7 product = models.ForeignKey( 8 Product, 9 on_delete=models.CASCADE 10 ) 11 12 name = models.CharField( 13 max_length=360, 14 blank=True, 15 null=True, 16 ) 17 18 image = models.FileField( 19 upload_to=get_mydesign_image_path, 20 null=True, 21 blank=True, 22 ) 23 24 def __str__(self): 25 return self.name

forms.py

1class CustomProductForm(forms.ModelForm): 2 3 class Meta: 4 model = MyDesign 5 fields = ['user', 'product', 'name', 'image']

views.py

1@method_decorator(staff_member_required, name='dispatch') 2class AddCustomProductAPIView(APIView): 3 """ カスタム商品追加API 4 管理画面のフォームからPOST 5 """ 6 7 def post(self, request, format=None, **kwargs): 8 data = request.data 9 file = request.FILES 10 11 print('request.data:', data) 12 print('request.FILES:', file) 13 14 try: 15 16 product_data = data.get('product', {}) 17 product_id = product_data.get('id') 18 product = Product.objects.filter(id=product_id).first() 19 20 form_data = { 21 'product': product, 22 'name': data.get('name'), 23 'image': request.FILES.get('image') 24 } 25 26 form = CustomProductForm(form_data) 27 if form.is_valid(): # 値が正しいか確認(バリデーション) 28 form.save() # 値をDBへの登録 29 else: 30 return Response({'errors': form.errors}, status=status.HTTP_400_BAD_REQUEST) 31 except Exception as e: 32 return Response({'message': 'Invalid form value.'}, status=status.HTTP_400_BAD_REQUEST) 33 return Response({'message': 'success'}, status=status.HTTP_200_OK)

試したこと

console.log()、print()を使用してファイルデータを確認。
vue.jsのconsole.log()で見る限り、その段階ではファイルが取得できていました。
(登録画面内で画像ファイルを確認できるようプレビュー表示していますがそれも問題なし。)

ただ、views.py側のprint()の出力結果は、
request.data: {'product': {'id': 1}, 'name': 'テスト', 'image': {}}
request.FILES: <MultiValueDict: {}>
となっており、request.FILESにimageのファイルが入っていないこと、request.dataの'image'が空であることが判明しました。

コメントを投稿

0 コメント