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

22,432

Solution 1

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!

Solution 2

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)

Solution 3

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.

Solution 4

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.

Share:
22,432

Related videos on Youtube

Kurtis
Author by

Kurtis

Freelance web-application developer who tries to specialize in Python/Django but is always exploring new technologies.

Updated on July 22, 2022

Comments

  • Kurtis
    Kurtis almost 2 years

    Essentially, I'm trying to find a good way to attach more views to a Router without creating a custom Router. What's a good way to accomplish this?

    Here is something sort of equivalent to what I'm trying to accomplish. Variable names have been changed and the example method I want to introduce is extremely simplified for the sake of this question.

    Router:

    router = routers.SimpleRouter(trailing_slash=False)
    router.register(r'myobjects', MyObjectViewSet, base_name='myobjects')
    urlpatterns = router.urls
    

    ViewSet

    class MyObjectsViewSet(viewsets.ViewSet):
    """ Provides API Methods to manage MyObjects. """
    
    def list(self, request):
        """ Returns a list of MyObjects. """
        data = get_list_of_myobjects()
        return Response(data)
    
    def retrieve(self, request, pk):
        """ Returns a single MyObject. """
        data = fetch_my_object(pk)
        return Response(data)
    
    def destroy(self, request, pk):
        """ Deletes a single MyObject. """
        fetch_my_object_and_delete(pk)
                return Response()
    

    One example of another method type I need to include. (There are many of these):

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

    The end-result is that the following URL would work correctly and be implemented 'cleanly'.

    GET example.com/myobjects/123/locations
    
  • Kurtis
    Kurtis over 10 years
    Quick and very precise answer. The only thing I would add is "from rest_framework.decorators import link". Thanks!
  • Kurtis
    Kurtis over 10 years
    If possible, could you (or anyone) also show me an example of adding a method to the Router which is not 'contained' within my ViewSet?
  • psychok7
    psychok7 over 9 years
    i am trying to upload a file to a custom method. do you know how i can achieve that?
  • rtindru
    rtindru over 8 years
    Is @action deprecated - The link suggests methods as arguments to the @detail_route decorator.
  • Roel
    Roel over 7 years
    Hi im new to django, what would be the end point for this? thanks!
  • cezar
    cezar over 6 years
    @ShiftN'Tab If you have myobjects-list and myobjects-detail, then this will be myobjects-locations.
  • NFern
    NFern almost 5 years
    '@detail_route' and '@list_route' are marked for deprecation. django-rest-framework.org/community/3.8-announcement/…