Python Django REST Framework 上传图片:“提交的数据不是文件”

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28036404/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 02:39:30  来源:igfitidea点击:

Django REST Framework upload image: "The submitted data was not a file"

pythonangularjsdjangodjango-rest-framework

提问by Lelouch

I am leaning how to upload file in Django, and here I encounter a should-be-trivial problem, with the error:

我正在学习如何在 Django 中上传文件,在这里我遇到了一个应该是微不足道的问题,错误是:

The submitted data was not a file. Check the encoding type on the form.

提交的数据不是文件。检查表单上的编码类型。

Below is the detail.

下面是细节。



Note:I also looked at Django Rest Framework ImageField, and I tried

注意:我也看了Django Rest Framework ImageField,我试过了

serializer = ImageSerializer(data=request.data, files=request.FILES)

but I get

但我明白了

TypeError: __init__()got an unexpected keyword argument 'files'

类型错误:__init__()有一个意外的关键字参数“文件”



I have a Imagemodel which I would like to interact with via Django REST framework:

我有一个Image模型,我想通过 Django REST 框架与之交互:

models.py

模型.py

class Image(models.Model):
    image = models.ImageField(upload_to='item_images')
    owner = models.ForeignKey(
        User, related_name='uploaded_item_images',
        blank=False,
    )
    time_created = models.DateTimeField(auto_now_add=True)

serializers.py

序列化程序.py

class ImageSerializer(serializers.ModelSerializer):
    image = serializers.ImageField(
        max_length=None, use_url=True,
    )

    class Meta:
        model = Image
        fields = ("id", 'image', 'owner', 'time_created', )

settings.py

设置.py

'DEFAULT_PARSER_CLASSES': (
    'rest_framework.parsers.JSONParser',
    'rest_framework.parsers.FormParser',
    'rest_framework.parsers.MultiPartParser',
),

The front end (using AngularJS and angular-restmodor $resource) send JSONdata with ownerand imageof the form:

前端(使用 AngularJS 和angular-restmod$resourceJSONownerimage的形式发送数据:

Input:

输入:

{"owner": 5, "image": "data:image/jpeg;base64,/9j/4QqdRXhpZgAATU0A..."}

In the backend, request.datashows

在后台,request.data显示

{u'owner': 5, u'image': u'data:image/jpeg;base64,/9j/4QqdRXhpZgAATU0AKgAAA..."}

But then ImageSerializer(data=request.data).errorsshows the error

但随后ImageSerializer(data=request.data).errors显示错误

ReturnDict([('image', [u'The submitted data was not a file. Check the encoding type on the form.'])])

I wonder what I should do to fix the error?

我想知道我应该怎么做才能修复错误?



EDIT: JS part

编辑:JS部分

The related front endcodes consists of two parts: a angular-file-dnddirective(available here) to drop the file onto the page and angular-restmod, which provides CRUD operations:

相关的前端代码由两部分组成:a angular-file-dnddirective(可在此处获得)将文件拖放到页面上angular-restmod,以及提供 CRUD 操作:

<!-- The template: according to angular-file-dnd, -->
<!-- it will store the dropped image into variable $scope.image -->
<div file-dropzone="[image/png, image/jpeg, image/gif]" file="image" class='method' data-max-file-size="3" file-name="imageFileName">
  <div layout='row' layout-align='center'>
    <i class="fa fa-upload" style='font-size:50px;'></i>
  </div>
  <div class='text-large'>Drap & drop your photo here</div>
</div>



# A simple `Image` `model` to perform `POST`
$scope.image_resource = Image.$build();

$scope.upload = function() {
  console.log("uploading");
  $scope.image_resource.image = $scope.image;
  $scope.image_resource.owner = Auth.get_profile().user_id;
  return $scope.image_resource.$save();
};




An update concerning the problem: right now I switched to using ng-file-upload, which sends image data in proper format.

关于该问题的更新:现在我切换到使用ng-file-upload,它以正确的格式发送图像数据。

采纳答案by Kevin Brown

The problem that you are hitting is that Django REST framework expects files to be uploaded as multipart form data, through the standard file upload methods. This is typically a filefield, but the JavaScript Blobobject also worksfor AJAX.

您遇到的问题是 Django REST 框架希望通过标准文件上传方法将文件作为多部分表单数据上传。这通常是一个filefield,但JavaScriptBlob对象也适用于 AJAX。

You are looking to upload the files using a base64 encoded string, instead of the raw file, which is not supported by default. There are implementations of a Base64ImageFieldout there, but the most promising one came by a pull request.

您希望使用 base64 编码字符串而不是原始文件上传文件,默认情况下不支持原始文件。还有的实现Base64ImageField在那里,但最有前途的一个走过来拉请求

Since these were mostly designed for Django REST framework 2.x, I've improved upon the one from the pull request and created one that should be compatible with DRF 3.

由于这些主要是为 Django REST framework 2.x 设计的,我已经改进了 pull request 中的一个,并创建了一个应该与 DRF 3 兼容的。

serializers.py

序列化程序.py

from rest_framework import serializers    

class Base64ImageField(serializers.ImageField):
    """
    A Django REST framework field for handling image-uploads through raw post data.
    It uses base64 for encoding and decoding the contents of the file.

    Heavily based on
    https://github.com/tomchristie/django-rest-framework/pull/1268

    Updated for Django REST framework 3.
    """

    def to_internal_value(self, data):
        from django.core.files.base import ContentFile
        import base64
        import six
        import uuid

        # Check if this is a base64 string
        if isinstance(data, six.string_types):
            # Check if the base64 string is in the "data:" format
            if 'data:' in data and ';base64,' in data:
                # Break out the header from the base64 content
                header, data = data.split(';base64,')

            # Try to decode the file. Return validation error if it fails.
            try:
                decoded_file = base64.b64decode(data)
            except TypeError:
                self.fail('invalid_image')

            # Generate file name:
            file_name = str(uuid.uuid4())[:12] # 12 characters are more than enough.
            # Get the file name extension:
            file_extension = self.get_file_extension(file_name, decoded_file)

            complete_file_name = "%s.%s" % (file_name, file_extension, )

            data = ContentFile(decoded_file, name=complete_file_name)

        return super(Base64ImageField, self).to_internal_value(data)

    def get_file_extension(self, file_name, decoded_file):
        import imghdr

        extension = imghdr.what(file_name, decoded_file)
        extension = "jpg" if extension == "jpeg" else extension

        return extension

This should be used in replacement of the standard ImageFieldprovided by Django REST framework. So your serializer would become

这应该用于替换ImageFieldDjango REST 框架提供的标准。所以你的序列化器会变成

class ImageSerializer(serializers.ModelSerializer):
    image = Base64ImageField(
        max_length=None, use_url=True,
    )

    class Meta:
        model = Image
        fields = ("id", 'image', 'owner', 'time_created', )

This should allow you to either specify a base64-encoded string, or the standard Blobobject that Django REST framework typically expects.

这应该允许您指定 base64 编码的字符串,或者BlobDjango REST 框架通常期望的标准对象。

回答by levi

I ran in the same problem few days ago. Here is my django rest framework view to handle file uploading

几天前我遇到了同样的问题。这是我处理文件上传的 django rest 框架视图

views.py

视图.py

class PhotoUploadView(APIView):
    parser_classes = (FileUploadParser,)

    def post(self, request):
        user = self.request.user
        if not user:
            return Response(status=status.HTTP_403_FORBIDDEN)
        profile  = None
        data     = None
        photo    = None

        file_form = FileUploadForm(request.POST,request.FILES)
        if file_form.is_valid():
            photo = request.FILES['file']
        else:
            return Response(ajax_response(file_form),status=status.HTTP_406_NOT_ACCEPTABLE)

        try:
            profile = Organizer.objects.get(user=user)
            profile.photo = photo
            profile.save()
            data    = OrganizersSerializer(profile).data
        except Organizer.DoesNotExist:
            profile = Student.objects.get(user=user)
            profile.photo = photo
            profile.save()
            data    = StudentsSerializer(profile).data

        return Response(data)

In front-end, I used angular-file-uploadlib.

在前端,我使用了angular-file-upload库。

Here is my file input

这是我的文件输入

<div ng-file-drop="" ng-file-select="" ng-model="organizer.photo" class="drop-box" drag-over-class="{accept:'dragover', reject:'dragover-err', delay:100}" ng-multiple="false" allow-dir="true" accept="image/*">
                                    Drop Images or PDFs<div>here</div>
</div>

And here is my upload service

这是我的上传服务

main.js

主文件

(function () {
  'use strict';

  angular
    .module('trulii.utils.services')
    .factory('UploadFile', UploadFile);

  UploadFile.$inject = ['$cookies', '$http','$upload','$window','Authentication'];

  /**
  * @namespace Authentication
  * @returns {Factory}
  */
  function UploadFile($cookies, $http,$upload,$window,Authentication) {
    /**
    * @name UploadFile
    * @desc The Factory to be returned
    */


    var UploadFile = {
      upload_file: upload_file,
    };

    return UploadFile;


    function upload_file(file) {


      return $upload.upload({
        url: '/api/users/upload/photo/', // upload.php script, node.js route, or servlet url
        //method: 'POST' or 'PUT',
        //headers: {'Authorization': 'xxx'}, // only for html5
        //withCredentials: true,
        file: file, // single file or a list of files. list is only for html5
        //fileName: 'doc.jpg' or ['1.jpg', '2.jpg', ...] // to modify the name of the file(s)
        //fileFormDataName: myFile, // file formData name ('Content-Disposition'), server side request form name
                                    // could be a list of names for multiple files (html5). Default is 'file'
        //formDataAppender: function(formData, key, val){}  // customize how data is added to the formData. 
                                                            // See #40#issuecomment-28612000 for sample code

      })

    }


  }



})();