web-dev-qa-db-de.com

Django REST Framework upload image: "Die übermittelten Daten waren keine Datei"

Ich lehne mich an, wie man Dateien in Django hochlädt, und hier stoße ich auf ein eigentlich triviales Problem mit dem Fehler:

Die übermittelten Daten waren keine Datei. Überprüfen Sie den Codierungstyp auf dem Formular.

Unten ist das Detail.


Hinweis: Ich habe mir auch Django Rest Framework ImageField angesehen und es versucht

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

aber ich verstehe

TypeError: __init__() hat ein unerwartetes Schlüsselwortargument "files" erhalten


Ich habe ein Image Modell, mit dem ich über das Django REST Framework interagieren möchte:

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',
),

Das Frontend (mit AngularJS und angular-restmod Oder $resource) Sendet JSON Daten mit owner und image des Formulars:

Eingang:

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

Im Backend wird request.data Angezeigt

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

Aber dann zeigt ImageSerializer(data=request.data).errors den Fehler

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

Ich frage mich, was ich tun soll, um den Fehler zu beheben.


EDIT: JS-Part

Der zugehörige Front-End-Code besteht aus zwei Teilen: a angular-file-dnddirective (verfügbar hier =) um die Datei auf der Seite abzulegen und angular-restmod, das CRUD-Operationen bereitstellt:

<!-- 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();
};


Ein Update zum Problem: Im Moment habe ich auf ng-file-upload Umgestellt, das Bilddaten im richtigen Format sendet.

38
Lelouch

Das Problem, das Sie treffen, ist, dass Django REST framework erwartet, dass Dateien als mehrteilige Formulardaten hochgeladen werden über die Standarddatei Upload-Methoden: Dies ist normalerweise ein file -Feld , aber das JavaScript Blob -Objekt funktioniert auch für AJAX.

Sie möchten die Dateien mit einer Base64-codierten Zeichenfolge anstelle der unformatierten Datei hochladen, die wird standardmäßig nicht unterstützt . Es gibt Implementierungen eines Base64ImageField da draußen , aber der vielversprechendste kam durch eine Pull-Anfrage .

Da diese hauptsächlich für Django REST framework 2.x) entwickelt wurden, habe ich die aus der Pull-Anfrage verbesserte Version verbessert und eine Version erstellt, mit der kompatibel sein sollte DRF 3.

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

Dies sollte als Ersatz für den Standard ImageField verwendet werden, der von Django REST Framework bereitgestellt wird. So würde Ihr Serializer werden

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

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

Dies sollte es Ihnen ermöglichen, entweder eine Base64-codierte Zeichenfolge oder das Standardobjekt Blob anzugeben, das das Django REST Framework normalerweise erwartet.

61
Kevin Brown

Ich habe vor ein paar Tagen das gleiche Problem gehabt. Hier ist meine Django Rest-Framework-Ansicht zum Hochladen von Dateien

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)

Im Front-End habe ich Angular-File-Upload lib verwendet.

Hier ist meine Dateieingabe

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

Und hier ist mein 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

      })

    }


  }



})();
3
levi