Search code examples
pythondjangoresturl-routing

Include list_route methods in Django REST framework's API root


I'm using the Django REST framework, and I have a view set with an extra list route method. How can I get that method's URL included in the API root page?

Here's a simplified version of my view set:

class BookViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = (permissions.IsAuthenticated, )

    @list_route(methods=['get'])
    def featured(self, request):
        queryset = self.filter_queryset(self.get_queryset()).filter(featured=True)

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

I register the view set in urls.py:

router = DefaultRouter()
router.register('books', BookViewSet)
urlpatterns = patterns(
    url(r'^api/', include(router.urls), name='api_home'),
    #...
    )

The URL for books/featured is routed properly, but when I go to http://localhost:8000/api, I only see this:

HTTP 200 OK
Content-Type: application/json
Vary: Accept
Allow: GET, HEAD, OPTIONS

{
    "books": "http://localhost:8000/api/books/"
}

How can I get an entry added for something like this?

"book-featured-list": "http://localhost:8000/api/books/featured"

Solution

  • You could try to inherit from DefaultRouter which is responsible for api root view and redefine get_api_root_view method.

    class MyRouter(routers.DefaultRouter):
        def get_api_root_view(self):
            """
            Return a view to use as the API root.
            """
            api_root_dict = OrderedDict()
            list_name = self.routes[0].name
            for prefix, viewset, basename in self.registry:
                api_root_dict[prefix] = list_name.format(basename=basename)
    
            class APIRoot(views.APIView):
                _ignore_model_permissions = True
    
                def get(self, request, *args, **kwargs):
                    ret = OrderedDict()
                    namespace = request.resolver_match.namespace
                    for key, url_name in api_root_dict.items():
                        if namespace:
                            url_name = namespace + ':' + url_name
                        try:
                            ret[key] = reverse(
                                url_name,
                                args=args,
                                kwargs=kwargs,
                                request=request,
                                format=kwargs.get('format', None)
                            )
                        except NoReverseMatch:
                            # Don't bail out if eg. no list routes exist, only detail routes.
                            continue
    
                    ret['book-featured-list'] = '%s%s' % (ret['books'], 'featured/')
    
                    return Response(ret)
    
            return APIRoot.as_view()
    

    P.S. sorry, didn't see your comment before I posted the answer