Drf-yasg: Wrong schema for FileField in model with default parser classes

Created on 17 Jun 2019  路  7Comments  路  Source: axnsan12/drf-yasg

When using a FileField in the model, a ModelSerializer, and a ModelViewSet DRF generates a endpoint where the file is uploaded using the MIME type multipart/form-data.

drf-yasg does not recognize this and instead skips the File field in the request but includes the other parameters in the scheme as JSON body parameters.

When setting the parser classes parser_classes = [FormParser, MultiPartParser] on the viewset the correct scheme is generated using form parameters. Unfortunately this is not an option because the update endpoint of the ModelViewSet requires the JSONParser.
Also adding the parameter manually as a form parameter does not work, this error is thrown: https://github.com/axnsan12/drf-yasg/blob/e9f27442fce1ce6df7aa33117e850b63a86a4051/src/drf_yasg/inspectors/view.py#L166

Most helpful comment

I know this response is way late but it may help others who stumble here.

The comments here clarified the problem quite well. The only way to have file upload in swagger is to make the request accept form parameters instead of json. You can use @action on a viewset, the parser_classes class attr, or override the get_parsers method to achieve this.

Perhaps drf-yasg could add an option to @swagger_auto_schema where you could manually set the consumes media type? Or perhaps drf-yasg could set the consumes media type to multipart form if there's a multipart parser and a FileField?

You can subclass SwaggerAutoSchema and implement that logic yourself, maybe using **extra_overrides/self.overrides. Changing the return of get_consumes should be enough.

All 7 comments

I'm dealing with the same problem. However, it looks like the accepted mimetypes for an endpoint that accepts a file upload is json and multipart/form-data:

(Pdb) request.parsers
[<rest_framework.parsers.JSONParser object at 0x7f2eb3352b00>, <rest_framework.parsers.FormParser object at 0x7f2eb3352b38>, <rest_framework.parsers.MultiPartParser object at 0x7f2eb3352b70>]

I assume that somehow drf-yasg is picking the JSONParser and then excluding the FileField because it can't be submitted via json.

You can customize the parser_classes with the @action decorator but I don't think that it's available for predefined routes like create, update, etc. At least, I get the error: Cannot use the @action decorator on the following methods, as they are existing routes: create when I try to do so.

Perhaps drf-yasg could add an option to @swagger_auto_schema where you could manually set the consumes media type? Or perhaps drf-yasg could set the consumes media type to multipart form if there's a multipart parser and a FileField?

This patch worked for me although I am sure there's a better way to do this:

diff --git a/src/drf_yasg/inspectors/view.py b/src/drf_yasg/inspectors/view.py
index 334c274..c7d8c06 100644
--- a/src/drf_yasg/inspectors/view.py
+++ b/src/drf_yasg/inspectors/view.py
@@ -29,6 +29,12 @@ class SwaggerAutoSchema(ViewInspector):
         consumes = self.get_consumes()
         produces = self.get_produces()

+        multipart = ['multipart/form-data', 'application/x-www-form-urlencoded']
+        if 'file' in [param['type'] for param in self.get_request_body_parameters(multipart)]:
+            # automatically set the media type to form data if there's a file
+            # needed due to https://github.com/axnsan12/drf-yasg/issues/386
+            consumes = multipart
+
         body = self.get_request_body_parameters(consumes)
         query = self.get_query_parameters()
         parameters = body + query

:+1:

No matter if you set manually parser_classes = (MultiPartParser, JSONParser) or parser_classes = (JSONParser, MultiPartParser), the MultiPartParser parser would be ignored... You must set it alone: parser_classes = (MultiPartParser,)

And I found the guilty: https://github.com/axnsan12/drf-yasg/blob/master/src/drf_yasg/utils.py#L380

There is no comment so I'm just wondering why...?? Sure there is a good reason to do this??

Indeed, because by default we have the following parsers:
'DEFAULT_PARSER_CLASSES': [ 'rest_framework.parsers.JSONParser', 'rest_framework.parsers.FormParser', 'rest_framework.parsers.MultiPartParser', ],
@See https://github.com/encode/django-rest-framework/blob/master/rest_framework/settings.py#L10

we should always returns just the application/json media type if you don't override the parser classes...
So it means we must always override parser_classes to set it only to MultiPartParser when you have to play with file/images (or at least, returns only form media types). Which also means we have to manage to have only one endpoint in order to override its parsers only (with modelviewset, some other endpoints may require non form media types, aka application/json, like retrieve, list, etc?

Sure not... Maybe a good way would be to allow multiples media types and let' the end-user select it into the drop-down list in Swagger UI for parameters? (no idea for redoc)

But... is that possible? No restriction with the core API?
I'll try to dig into!

I have created a pull request: https://github.com/axnsan12/drf-yasg/pull/436
Not sure if this patch is good so awaiting for a better fix...

Ok bad news, it seens to not be possible to enforce this media content type without having to set it manually... See the merge request update with comments... :/

I know this response is way late but it may help others who stumble here.

The comments here clarified the problem quite well. The only way to have file upload in swagger is to make the request accept form parameters instead of json. You can use @action on a viewset, the parser_classes class attr, or override the get_parsers method to achieve this.

Perhaps drf-yasg could add an option to @swagger_auto_schema where you could manually set the consumes media type? Or perhaps drf-yasg could set the consumes media type to multipart form if there's a multipart parser and a FileField?

You can subclass SwaggerAutoSchema and implement that logic yourself, maybe using **extra_overrides/self.overrides. Changing the return of get_consumes should be enough.

Why does it ignore the parser_classes decorator on a view set method?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

iamhssingh picture iamhssingh  路  3Comments

nicholasgcoles picture nicholasgcoles  路  4Comments

Safrone picture Safrone  路  3Comments

csdenboer picture csdenboer  路  3Comments

hnykda picture hnykda  路  3Comments