[Django]-Adding more views to a Router or viewset (Django-Rest-Framework)

24👍

The answer given by mariodev above is correct, as long as you’re only looking to make GET requests.

If you want to POST to a function you’re appending to a ViewSet, you need to use the action decorator:

from rest_framework.decorators import action, link
from rest_framework.response import Response

class MyObjectsViewSet(viewsets.ViewSet):

    # For GET Requests
    @link()
    def get_locations(self, request):
        """ Returns a list of location objects somehow related to MyObject """
        locations = calculate_something()
        return Response(locations)

    # For POST Requests
    @action()
    def update_location(self, request, pk):
        """ Updates the object identified by the pk """
        location = self.get_object()
        location.field = update_location_field() # your custom code
        location.save()

        # ...create a serializer and return with updated data...

Then you would POST to a URL formatted like:
/myobjects/123/update_location/

http://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing has more information if you’re interested!

15👍

You can now do this with the list_route and detail_route decorators: http://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing

For example:

from rest_framework.decorators import list_route
from rest_framework.response import Response
...

class MyObjectsViewSet(viewsets.ViewSet):
    ...

    @list_route()
    def locations(self, request):
        queryset = get_locations()
        serializer = LocationSerializer(queryset, many=True)
        return Response(serializer.data)

8👍

You define method like you do now, but you need to use the same url as method name and add link decorator, so for

/myobjects/123/locations/

You add method like this

@link(permission_classes=[...])
def locations(self, request, pk=None):
    ...

and router will pick it automatically.

3👍

From Routing to extra methods on a ViewSet:

I think you may need to route the method by hand, i.e. The Old-Fashioned Way™.

First pull the method out as a separate view:

   set_password_view = UserViewSet.as_view({'post': 'set_password'})

(or such)

Then assign your URL:

   url(r'^users/username_available/$', set_password_view, name-=...)

(Or such)

There’s a related question on SO.

0👍

If you want to extend a viewset with a view that is or should not directly be written inside your viewset, you can write a “wrapper” action to pass the data through.

For example, with class based views:

from somewhere import YourExternalClassView


class SomeViewSet(viewsets.ReadOnlyModelViewSet):

    # ...

    @action(detail=True)
    def your_action(self, request, pk):
        return YourExternalClassView.as_view()(request, pk=pk)

How does it work?

On class based views, the as_view method returns a view function, to which we will pass the data we received from the action. The view will then hand over to process further.

For non-class based view, the views can be called/wrapped directly without .as_view(...)(...).

Leave a comment