23

I created a simple Model with an ImageField and I wanna make an api view with django-rest-framework + django-rest-swagger, that is documented and is able to upload the file.

Here is what I got:

models.py

from django.utils import timezone
from django.db import models

class MyModel(models.Model):

    source = models.ImageField(upload_to=u'/photos')
    is_active = models.BooleanField(default=False)
    created_at = models.DateTimeField(default=timezone.now)

    def __unicode__(self):
        return u"photo {0}".format(self.source.url)

serializer.py

from .models import MyModel

class MyModelSerializer(serializers.ModelSerializer):

    class Meta:
        model = MyModel
        fields = [
            'id',
            'source',
            'created_at',
        ]

views.py

from rest_framework import generics
from .serializer import MyModelSerializer

class MyModelView(generics.CreateAPIView):
    serializer_class = MyModelSerializer
    parser_classes = (FileUploadParser, )

    def post(self, *args, **kwargs):
        """
            Create a MyModel
            ---
            parameters:
                - name: source
                  description: file
                  required: True
                  type: file
            responseMessages:
                - code: 201
                  message: Created
        """
        return super(MyModelView, self).post(self, *args, **kwargs)

urls.py

from weddings.api.views import MyModelView

urlpatterns = patterns(
    '',
    url(r'^/api/mymodel/$', MyModelView.as_view()),
)

For me this should be pretty simple. However, I can't make the upload work. I always get this error response: enter image description here

I've read this part of the documentation from django-rest-framework:

If the view used with FileUploadParser is called with a filename URL keyword argument, then that argument will be used as the filename. If it is called without a filename URL keyword argument, then the client must set the filename in the Content-Disposition HTTP header. For example Content-Disposition: attachment; filename=upload.jpg.

However the Header is being passed by django-rest-swagger in the Request Payload property (from chrome console).

If any more info is necessary, please let me know.

I'm using Django==1.8.8, djangorestframework==3.3.2 and django-rest-swagger==0.3.4.

coffee-grinder
  • 26,940
  • 19
  • 56
  • 82
jarussi
  • 1,491
  • 1
  • 13
  • 25

3 Answers3

8

I got this working by making a couple of changes to your code.

First, in models.py, change ImageField name to file and use relative path to upload folder. When you upload file as binary stream, it's available in request.data dictionary under file key (request.data.get('file')), so the cleanest option is to map it to the model field with the same name.

from django.utils import timezone
from django.db import models


class MyModel(models.Model):

    file = models.ImageField(upload_to=u'photos')
    is_active = models.BooleanField(default=False)
    created_at = models.DateTimeField(default=timezone.now)

    def __unicode__(self):
        return u"photo {0}".format(self.file.url)

In serializer.py, rename source field to file:

class MyModelSerializer(serializers.ModelSerializer):

    class Meta:
        model = MyModel
        fields = ('id', 'file', 'created_at')

In views.py, don't call super, but call create():

from rest_framework import generics
from rest_framework.parsers import FileUploadParser

from .serializer import MyModelSerializer


class MyModelView(generics.CreateAPIView):
    serializer_class = MyModelSerializer
    parser_classes = (FileUploadParser,)

    def post(self, request, *args, **kwargs):
        """
            Create a MyModel
            ---
            parameters:
                - name: file
                  description: file
                  required: True
                  type: file
            responseMessages:
                - code: 201
                  message: Created
        """
        return self.create(request, *args, **kwargs)

I've used Postman Chrome extension to test this. I've uploaded images as binaries and I've manually set two headers:

Content-Disposition: attachment; filename=upload.jpg
Content-Type: */*
ozren1983
  • 1,891
  • 1
  • 16
  • 34
  • I tried the changes you suggested, but now I'm experiencing a different error: `Upload a valid image. The file you uploaded was either not an image or a corrupted image` – jarussi Apr 28 '16 at 21:03
6

This is the final solution I came up with:

from rest_framework import generics
from rest_framework.parsers import FormParser, MultiPartParser
from .serializer import MyModelSerializer

class MyModelView(generics.CreateAPIView):
    serializer_class = MyModelSerializer
    parser_classes = (FormParser, MultiPartParser)

    def post(self, *args, **kwargs):
        """
            Create a MyModel
            ---
            parameters:
                - name: source
                  description: file
                  required: True
                  type: file
            responseMessages:
                - code: 201
                  message: Created
        """
        return super(MyModelView, self).post(self, *args, **kwargs)

All I had to do was change the parsers from FileUploadParser to (FormParser, MultiPartParser)

jarussi
  • 1,491
  • 1
  • 13
  • 25
  • What do you need to do to get DRF or DRF-Swagger to pick up that yml? I set a docstring like that, but it just renders as Markdown under "Implementation Notes" – Aaron McMillin May 17 '17 at 13:24
  • I don't have much in my settings. I'ts pretty much the basic from the django-rest-swagger docs. However, I'm using version `0.3.7` of django-rest-swagger. – jarussi Jun 01 '17 at 12:44
3

It has been my experience that the FileUploadParser works with this format of a request:

    curl -X POST -H "Content-Type:multipart/form-data" \
                 -F "file=@{filename};type=image/jpg" \
                 https://endpoint.com/upload-uri/

The request.data['file'] in your view will have the file.

Maybe if you try a Content-Type:multipart/form-data header, you will have luck.

zemekeneng
  • 1,660
  • 2
  • 15
  • 26
  • Thank you zemekeneng, but I still get and error: `{"file":["The submitted file is empty."]}`. That did not worked. – jarussi Apr 28 '16 at 20:42
  • Maybe try calling it with `curl` and make sure that the file name is inside the `{}` so it looks like `file=@{path/to/file}`, if this works, then the issue might be client-side. Good luck! – zemekeneng Apr 28 '16 at 21:13