question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How to remove/prevent parameters inherited" from ViewSet

See original GitHub issue

I am migrating from drf_yasg to drf-spectacular and I think the below is not answered yet anywhere. I did read the docs 😃

I have some ViewSets to handle models via DRF, for example:

class EngagementViewSet(mixins.ListModelMixin,
                        mixins.RetrieveModelMixin,
                        mixins.UpdateModelMixin,
                        mixins.DestroyModelMixin,
                        mixins.CreateModelMixin,
                        ra_api.AcceptedRisksMixin,
                        viewsets.GenericViewSet):
    serializer_class = serializers.EngagementSerializer
    queryset = Engagement.objects.none()
    filter_backends = (DjangoFilterBackend,)
    filter_class = ApiEngagementFilter
    permission_classes = (IsAuthenticated, permissions.UserHasEngagementPermission)

    @swagger_auto_schema(
        request_body=no_body, responses={status.HTTP_200_OK: ""}
    )
    @extend_schema(
        request=no_body,
        responses={status.HTTP_200_OK: ""}
    )
    @action(detail=True, methods=["post"])
    def close(self, request, pk=None):
        eng = self.get_object()
        close_engagement(eng)
        return HttpResponse()

As you can see there is a custom Mixin ra.api.AcceptedRisksMixin to provides an extra action method:

class AcceptedFindingsMixin(ABC):

    @swagger_auto_schema(
        request_body=AcceptedRiskSerializer(many=True),
        responses={status.HTTP_201_CREATED: RiskAcceptanceSerializer(many=True)},
    )
    @extend_schema(
        request=AcceptedRiskSerializer(many=True),
        responses={status.HTTP_201_CREATED: RiskAcceptanceSerializer(many=True)},
    )
    @action(methods=['post'], detail=False, permission_classes=[IsAdminUser], serializer_class=AcceptedRiskSerializer)
    def accept_risks(self, request):
        serializer = AcceptedRiskSerializer(data=request.data, many=True)
        if serializer.is_valid():
            accepted_risks = serializer.save()
        else:
            return Response(data=serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        owner = request.user
        accepted_result = []
        for engagement in get_authorized_engagements(Permissions.Engagement_View):
            base_findings = engagement.unaccepted_open_findings
            accepted = _accept_risks(accepted_risks, base_findings, owner)
            engagement.accept_risks(accepted)
            accepted_result.extend(accepted)
        result = RiskAcceptanceSerializer(instance=accepted_result, many=True)
        return Response(status=201, data=result.data)

For drf-yasg it works as expected/desired, getting two parameters the id and request body in the schema:

       "/api/v2/engagements/{id}/accept_risks/": {
            "post": {
                "operationId": "engagements_accept_risks_create",
                "description": "",
                "parameters": [
                    {
                        "in": "path",
                        "name": "id",
                        "schema": {
                            "type": "integer"
                        },
                        "description": "A unique integer value identifying this engagement.",
                        "required": true
                    }
                ],
                "tags": [
                    "engagements"
                ],
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/AcceptedRisk"
                            }
                        },

For drf-spectacular I am getting a lot more parameters. I think these are “inherited” or discovered from the EngagementViewset where there is a filter backend that accepts all these parameters.

      "/api/v2/engagements/{id}/accept_risks/": {
            "post": {
                "operationId": "engagements_accept_risks_create",
                "description": "",
                "parameters": [
                    {
                        "in": "query",
                        "name": "active",
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "in": "query",
                        "name": "api_test",
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "in": "path",
                        "name": "id",
                        "schema": {
                            "type": "integer"
                        },
                        "description": "A unique integer value identifying this engagement.",
                        "required": true
                    },
                    {
                        "in": "query",
                        "name": "id",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "limit",
                        "required": false,
                        "in": "query",
                        "description": "Number of results to return per page.",
                        "schema": {
                            "type": "integer"
                        }
                    },

....


               "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "array",
                                "items": {
                                    "$ref": "#/components/schemas/AcceptedRisk"
                                }
                            }
                        },
           

I also notice pagination related schema elements in the response:

                "responses": {
                    "201": {
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/PaginatedRiskAcceptanceList"
                                }
                            }
                        },
                        "description": ""
                    }
                }

Is the expected / intended behaviour? Without stating which one is correct, I think the drf-yasg scheme is what I was expecting / looks the most intuitive.

I tried using @extend_schema, but it looks like that can only be used to add extra parameters, not to remove superfluous ones.

Is there anyway to prevent this “inheritance” of parameters and other elemens into the method from the MixIn?

I am not seeing all these “extra parameters” on @action methods declared directly in the ViewSet, such as close in the above example.

Full code is at: https://github.com/valentijnscholten/django-DefectDojo/blob/cd89dac2d92f5734c7c717edce39130caf6278f3/dojo/api_v2/views.py#L115

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:6 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
valentijnscholtencommented, May 21, 2021

Small tweak to use empty list needed as filter_backends=None caused some issues down the road:

    @action(methods=['post'], detail=True, permission_classes=[IsAdminUser], serializer_class=AcceptedRiskSerializer,
            filter_backends=[], pagination_class=None)
1reaction
tfranzelcommented, May 21, 2021

yes that makes sense. i wrote that example from memory. django-filter depends on both classes. perfect!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Viewsets - Django REST framework
Django, API, REST, Viewsets. ... Use the url_path and url_name parameters to change the URL segment and the ... The ViewSet class inherits...
Read more >
Disable a method in a ViewSet, django-rest-framework
Any idea on how to do this the right way? class SampleViewSet(viewsets.ModelViewSet): queryset = api_models.Sample.objects.all() serializer_class = ...
Read more >
Django REST Framework Views - ViewSets - TestDriven.io
When creating a router, you must provide two arguments: The URL prefix for the views; The ViewSet itself. Then, we included the router...
Read more >
ViewSets - Django REST framework - Tom Christie
The ViewSet class inherits from APIView . You can use any of the standard attributes such as permission_classes , authentication_classes in order to...
Read more >
Viewsets and Routers with React Front-end Example - Part-4
Hello, we are back again for the forth tutorial in this Django Rest Framework Series. In the following series of tutorials we will...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found