如何将图像文件传递到API端点

时间:2019-07-14 09:52:42

标签: json django web-services django-rest-framework django-forms

我有一个使用DRF的Web服务,我让POSTMAN正确地成功通过了所有字段,包括图像文件['avatar'] [ango] 1

现在如何从Django表单传递图像文件,但我不断收到错误400,错误的请求。请我如何编码要传递到API端点的图像文件。

html

<form method="post" action="#" enctype="multipart/form-data">
                          {% csrf_token %}
                              <label for="phone">Enter Phone</label>
                              {{ form.phone }} <br />
                              <label for="bvn">Enter BVN</label>
                              {{ form.bvn }} <br />
                              <label for="avatar">Upload Avatar</label>
                              {{ form.avatar }} <br />
                              <button type="submit" class="btn btn-primary" >Submit</button>

                          </form>

views.py

def form_valid(self, form):
    token = self.request.session['session_token']
    headers = {"Content-Type": 'application/json', "Authorization": "Token " + token}

    parameters = {
        'bvn': form.cleaned_data['bvn'],
        'phone': form.cleaned_data['phone'],
        'avatar': self.request.FILES['avatar']
    }
    response = requests.put(str(settings.API_END_POINT + '/customer_profile_api/'), data=parameters,
                             headers=headers)
    if response.status_code in settings.SUCCESS_CODES:
        messages.success(self.request, 'Successfully updated profile')
    else:
        messages.error(self.request, 'API Error %s' % response.status_code)

    return super(CustomerDashboard, self).form_valid(form)

form.py

class ProfileUpdateForm(forms.Form):
    """customer profile update form"""
    phone = forms.CharField(required=False, widget=NumberInput(attrs={'class': 'form-control',
                                                                      'type': 'number', 'id': 'phone',
                                                                      'placeholder': 'Enter Phone Number'}))
    bvn = forms.CharField(required=False, widget=NumberInput(attrs={'class': 'form-control',
                                                                      'type': 'number', 'id': 'bvn',
                                                                      'placeholder': 'Enter BVN'}))
    avatar = forms.ImageField(required=False, widget=ClearableFileInput(attrs={'class': 'form-control',
                                                                      'type': 'file', 'id': 'avatar',
                                                                      'placeholder': 'Upload Avatar'}))

2 个答案:

答案 0 :(得分:1)

您的标题错误。

headers = {"Content-Type": 'application/json', "Authorization": "Token " + token}

更改为:

headers = {"Content-Type": 'multipart/form-data', "Authorization": "Token " + token}

上传某些文件时,应使用multipart/form-data而不是application/json (如果您当然不将文件作为base64发送)

答案 1 :(得分:0)

如果其他人遇到相同的问题,我能够解决的唯一方法是使用单独的视图来处理图像和其他文本字段。到目前为止,我所做的工作奏效了。 如果您注意到,用于处理未插入图像的视图的内容类型标头,只需将其删除并让它由您的API自行处理,几次将其设置为multipart / form-data时,我将命中响应代码200 / 201,但是文件永远不会保存。

我的API views.py

class CustomerApiView(generics.UpdateAPIView, generics.ListAPIView):
    """Customer Api View to update customer profile"""
    http_method_names = ['put', 'get']
    renderer_classes = [renderers.JSONRenderer]
    authentication_classes = [authentication.TokenAuthentication]
    serializer_class = CustomerSerializer
    queryset = Profile.objects.all()
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        user = self.request.user
        return self.queryset.filter(user=user)

    def get_object(self):
        queryset = self.filter_queryset(self.get_queryset())
        # make sure to catch 404's below
        obj = queryset.get(user_id=self.request.user.id)
        self.check_object_permissions(self.request, obj)
        return obj

class CustomerAvatarApiView(generics.UpdateAPIView):
    """Customer Avatar/DP API View"""
    serializer_class = CustomerAvatarSerializer
    queryset = Profile.objects.all()
    authentication_classes = [authentication.TokenAuthentication]
    permission_classes = [IsAuthenticated]
    renderer_classes = [renderers.JSONRenderer]
    http_method_names = ['put', 'get']

    def get_object(self):
        queryset = self.filter_queryset(self.get_queryset())
        # make sure to catch 404's below
        obj = queryset.get(user_id=self.request.user.id)
        self.check_object_permissions(self.request, obj)
        return obj

serializer.py

class CustomerSerializer(serializers.ModelSerializer):
    """Customer Profile Serializer"""

    class Meta:
        """Class Meta"""
        model = Profile
        fields = ('pk', 'phone', 'bvn', 'avatar')
        extra_kwargs = {'pk': {'read_only': True}, }


class CustomerAvatarSerializer(serializers.ModelSerializer):
    """Separate Serializer to handle upload of file"""

    class Meta:
        """Class Meta"""
        model = Profile
        fields = ('avatar', )

我的view.py

class CustomerDashboard(LoginRequiredMixin, generic.FormView):
    """customer dashboard view"""
    template_name = 'customer/dashboard.html'
    form_class = ProfileUpdateForm
    raise_exception = False
    success_url = reverse_lazy('customer_dashboard')

    def get_profile_api(self):
        """get profile api call to fetch profile; passing in the token from session"""
        token = self.request.session['session_token']
        headers = {"Content-Type": 'application/json', "Authorization": "Token " + token}
        response = requests.get(str(settings.API_END_POINT + '/customer_profile_api/'), headers=headers)
        json_response = response.json()
        return json_response

    def get_initial(self):
        initial = ''
        try:
            initial = super(CustomerDashboard, self).get_initial()
            initial['phone'] = self.get_profile_api()[0]['phone']
            initial['bvn'] = self.get_profile_api()[0]['bvn']
        except:
            pass

        return initial

    def get_context_data(self, **kwargs):
        """overriding get_context_data to pass data to template"""
        context = super(CustomerDashboard, self).get_context_data()
        if self.get_profile_api()[0]['bvn'] != '':  # if BVN has been filled before pass True or False to template
            context['bvn_initial'] = True  # Todo Validate BVN from API Before calling save method on it
        else:
            context['bvn_initial'] = False

        return context

    def form_valid(self, form):
        token = self.request.session['session_token']

        if 'updateAvatar' in self.request.POST:

            headers = {"Authorization": "Token " + token}
            avatar = form.cleaned_data['avatar']

            file = {'avatar': (str(avatar), avatar)}

            response = requests.put(str(settings.API_END_POINT + '/customer_profile_avi_api/'), files=file,
                                    headers=headers)
            message = 'Avatar changes successfully'

            if response.status_code in settings.SUCCESS_CODES:
                messages.success(self.request, message)
            else:
                messages.error(self.request, 'API Error %s' % response)

        elif 'updateProfile' in self.request.POST:

            headers = {"Content-Type": "application/json", "Authorization": "Token " + token}
            parameters = {
                'bvn': form.cleaned_data['bvn'],
                'phone': form.cleaned_data['phone'],
            }

            response = requests.put(str(settings.API_END_POINT + '/customer_profile_api/'), json=parameters,
                                    headers=headers)
            message = 'Profile changes successfully'

            if response.status_code in settings.SUCCESS_CODES:
                messages.success(self.request, message)
            else:
                messages.error(self.request, 'API Error %s' % response)

        return super(CustomerDashboard, self).form_valid(form)