Andrii Yurchuk
Andrii Yurchuk

Reputation: 3270

POSTing to URL of one resource to create another, different resource

In my REST API I have two entities: Test and TestRun. I want to be able to send a POST request to create a TestRun (with the appropriate TestRun fields), but the URL of this request must be api/v1/test/{id}/start instead of api/v1/testrun. I know that using @detail_route I can customise the URL, but then the request is still sent to api/v1/test/{id}:

class TestViewSet(viewsets.ModelViewSet):
    queryset = Test.objects.all()
    serializer_class = TestSerializer

    @detail_route(methods=['post'], url_path='start')
    def start_test(self, request, pk=None):
        pass

class TestRunViewSet(viewsets.ModelViewSet):
    queryset = TestRun.objects.all()
    serializer_class = TestRunSerializer

Perhaps some highly customised router is needed here?

Upvotes: 0

Views: 164

Answers (1)

opalczynski
opalczynski

Reputation: 1647

OK, I have the basic example. I think you have few problems, so first things first:

My views:

class TestViewSet(viewsets.ModelViewSet):
    queryset = Test.objects.all()
    serializer_class = TestSerializer

    @detail_route(methods=['post'], url_path='start', serializer_class=TestRunSerializer)
    def start_test(self, request, pk=None):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            # add here TestRun object
            return Response(serializer.data, status=status.HTTP_200_OK)


class TestRunViewSet(viewsets.ModelViewSet):
    queryset = TestRun.objects.all()
    serializer_class = TestRunSerializer

My urls:

router = SimpleRouter()
router.register('test', TestViewSet)
router.register('test-run', TestRunViewSet)

urlpatterns = router.urls

and settings urls:

urlpatterns = [
    url(r'^api/v1/', include('droute.urls'))
]

In this scenario you have full CRUD for Test and TestRun models - one is under api/vi/test and second in api/v1/test-run;

The detail_route decorator creates for you additional route: /api/v1/test/:id/start

But this do not mean that CRUD under api/v1/test-run is no longer accessible.

If you do not want to do not allow creation on api/v1/test-run you should use there ReadOnlyModelViewSet as a base for TestRunViewSet - this will allow only GET on the list endpoint: api/v1/test-run and on the details endpoint: api/v1/test-run//

You do not need to make magic in routers - as in example SimpleRouter is enough for that case.

Things are getting little bit more complicated if you want to make nested routers. You can search stackoverflow - there were many articles about that. But to be honest I would discourage you to use nested routers, I never feel that working with this is a pleasure :) You can check here: https://github.com/alanjds/drf-nested-routers

I think (but I have little or nono information) that the best API for you would be something like this:

  • /api/v1/test -> CRUD for TEST
  • /api/v1/test/:id/start -> start the test POST
  • /api/v1/test/:id/runs -> get the runs list GET (list_route on TestViewSet or Nested router)
  • /api/v1/test/:id/runs/:run_id -> get the run details GET (and here is a problem - because it implies that you need nesting :(, or some custom view attached to the urls;)

Happy coding, hope this helps.

Upvotes: 1

Related Questions