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

时间:2022-09-15 21:53:35

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框架ImageField,并尝试了

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

but I get

但我得到

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

TypeError: __init__()得到一个意外的关键字参数'files'


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

我有一个图像模型,我想通过Django REST框架与它交互:

models.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

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

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

The front end (using AngularJS and angular-restmod or $resource) send JSON data with owner and image of the form:

前端(使用AngularJS和angular-restmod或$resource)向表单的所有者和图像发送JSON数据:

Input:

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

In the backend, request.data shows

在后台,请求。数据显示

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

But then ImageSerializer(data=request.data).errors shows the error

但后来ImageSerializer(数据= request.data)。错误显示错误

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 end codes consists of two parts: a angular-file-dnd directive (available here) to drop the file onto the page and angular-restmod, which provides CRUD operations:

相关的前端代码由两个部分组成:一个是angular-file-dnd指令(这里有),用于将文件放到页面上;另一个是提供CRUD操作的angular-restmod:

<!-- 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,它以适当的格式发送图像数据。

2 个解决方案

#1


46  

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 file field, but the JavaScript Blob object also works for AJAX.

您遇到的问题是Django REST框架希望通过标准的文件上传方法将文件作为多部分表单数据上载。这通常是一个文件字段,但是JavaScript Blob对象也适用于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 Base64ImageField out 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框架2设计的。我已经从pull请求中改进了一个,并创建了一个与drf3兼容的版本。

serializers.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 ImageField provided by Django REST framework. So your serializer would become

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

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 Blob object that Django REST framework typically expects.

这将允许您指定一个base64编码的字符串,或者Django REST框架通常期望的标准Blob对象。

#2


3  

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

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

views.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-upload lib.

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

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

      })

    }


  }



})();

#1


46  

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 file field, but the JavaScript Blob object also works for AJAX.

您遇到的问题是Django REST框架希望通过标准的文件上传方法将文件作为多部分表单数据上载。这通常是一个文件字段,但是JavaScript Blob对象也适用于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 Base64ImageField out 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框架2设计的。我已经从pull请求中改进了一个,并创建了一个与drf3兼容的版本。

serializers.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 ImageField provided by Django REST framework. So your serializer would become

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

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 Blob object that Django REST framework typically expects.

这将允许您指定一个base64编码的字符串,或者Django REST框架通常期望的标准Blob对象。

#2


3  

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

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

views.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-upload lib.

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

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

      })

    }


  }



})();