Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

no way to specify parser_classes for views in a viewset #266

Closed
NANASHI0X74 opened this issue Jan 20, 2021 · 6 comments
Closed

no way to specify parser_classes for views in a viewset #266

NANASHI0X74 opened this issue Jan 20, 2021 · 6 comments
Labels
fix confirmation pending issue has been fixed and confirmation from issue reporter is pending

Comments

@NANASHI0X74
Copy link

NANASHI0X74 commented Jan 20, 2021

Describe the bug
I've got a DocumentViewset that's used to both upload and retrieve files. For the create and update actions, the only sensible parser is MultiPartParser, but all other actions should accept json. So I don't want to set parser_classes on the whole viewset and it looks like there is no such thing as get_parser_classes akin to what can be used for serializers in drf.

There is an annotation in drf that can be used for function based views to override parser_classes, but that won't work for single views in viewsets afaict.

Maybe in this case it would make sense to have an override for parser_classes in extend_schema just like for request/response serializers?

An example:

class DocumentViewSet(viewsets.ModelViewSet):
    permission_classes = (DocumentPermission,)
    queryset = Document.objects.all()
    
    def get_parser_classes(self):
        if self.action == 'create':
            return [MultiPartParser]
        else:
            return [JSONParser]
        
    def get_serializer_class(self):
        if self.action in ('create', 'partial_update', 'update'):
            return DocumentEditSerializer
        elif self.action == 'retrieve':
            return DocumentDetailSerializer
        else:
            return DocumentListSerializer

    def perform_create(self, serializer):
        kwargs = {
            'creator': self.request.user,
            'company': self.request.user.company
        }

        serializer.save(**kwargs)

The resulting schema for the above includes all three of the default parsers:

   operationId: documents_create
      description: ''
      tags:
      - documents
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DocumentEditRequest'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/DocumentEditRequest'
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/DocumentEditRequest'
@tfranzel
Copy link
Owner

there is get_parsers() on views but it is only called once for the whole view. this is a DRF limitation and has little to do with spectacular. best you can do is either split the views or do this

    class XViewset(viewsets.ModelViewSet):
        serializer_class = M3Serializer
        queryset = M3.objects.none()
        parser_classes = [parsers.MultiPartParser]

        @action(methods=['POST'], detail=False, parser_classes=[parsers.JSONParser])
        def json_only_action(self, request):
            pass

this will get properly detected. for requests we have this:

    @extend_schema(responses={
        (200, 'application/json'): OpenApiTypes.OBJECT,
        (200, 'application/pdf'): OpenApiTypes.BINARY,
    })

something similar could be constructed for requests. however, parsers_classes is more elegant imho as it actually does what it says.

@podplatnikm
Copy link

podplatnikm commented Feb 19, 2021

Hello, I have the same issue as OP (I would like to set multipart/form-data for only the 'create' method on ModelViewSet). I can set the MultiPartParser in the parser_classes or by redefining 'get_parsers' and it works great, but changes are not reflected in the Swagger. Now, there may be an option to redefine this but I have yet to find it.
I also do not really understand your answer @tfranzel, I cant use the action decorator on the default 'create' view and the second code snipper is more about responses, which are working fine with me.
Any help would be appreciated, have a nice day!

EDIT: Typo

@tfranzel
Copy link
Owner

get_parser_classes (as mentioned above) does not exist. get_parsers does exist but is called very early in the process before there is self.method, self.action or self.request available.

so there is no way you can dynamically adapt the parser. there is only @action(parser_classes=X). this is what i meant with DRF's limitation. note that this is different for get_renderers, so dynamically changing the renderer would work.

with the second part I meant we might add an extend_schema override for request similar to responses. this was just added. usage e.g.: @extend_schema(request={'application/pdf': bytes})

the other fix improves the method case and I would say that this now models exactly how DRF behaves.

@podplatnikm @NANASHI0X74 please test this. i consider this issue resolved with the added feature and improved detection.

@tfranzel tfranzel added the fix confirmation pending issue has been fixed and confirmation from issue reporter is pending label Feb 23, 2021
@NANASHI0X74
Copy link
Author

oooh nice 👍
I'll test this tomorrow. In this case with a modelviewset I have to use the extend_schema_view annotation on the class, right?

@NANASHI0X74
Copy link
Author

I totally forgot about this yesterday... I'll try it next week though now, because I got an urgent task for this week today

@NANASHI0X74
Copy link
Author

looks like the schema generated is correct now-
I added the following annotation to the class:

@extend_schema_view(
    create=extend_schema(request={'multipart/form-data': DocumentEditSerializer})
)
class DocumentViewSet(viewsets.ModelViewSet):

And the schema generated is:

  /documents/
    post:         
      operationId: documents_create
      description: ''
      tags:                                              
      - documents        
      requestBody:  
        content:
          multipart/form-data:         
            schema:  
              $ref: '#/components/schemas/DocumentEditRequest'

'#/components/schemas/DocumentEditRequest':

    DocumentEditRequest:                                      
      type: object                          
      properties:                                   
        creator:                                              
          type: integer       
          nullable: true                                      
        company:                                              
          type: integer
          nullable: true                                      
        name:                 
          type: string
          maxLength: 255                                      
        description:                   
          type: string       
          nullable: true   
          maxLength: 255                                 
        filepath:                                          
          type: string   
          format: binary                   
          nullable: true           

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
fix confirmation pending issue has been fixed and confirmation from issue reporter is pending
Projects
None yet
Development

No branches or pull requests

3 participants