Possible to have a method on an APIView called from a url

21,045

Solution 1

Both of the answers posted here by @knbk and @almalki are both valid approaches to what I was asking. However they are not showing what I actually ended up doing after a few hours or so of looking into it.

I ended up using ViewSets that allowed me to bind the GET, POST, etc requests to a certain function in a ViewSet class. Usually you would use routers to bind all of the appropriate functions automatically, but I wanted a little more flexibility with how they were bound so I just write them out myself.

# views.py
class CartViewSet(ViewSet):
    def clear(self, request):
        """Clear the users cart."""
        queryset = Cart.objects.get(user=request.user)

        queryset.clear_cart()

        serializer = CartSerializer(queryset)

        return Response(serializer.data, status=status.HTTP_200_OK)

clear_cart_viewset = CartViewSet.as_view({
    'post': 'clear'
})

# urls.py
urlpatterns = patterns('app.views',
    ....
    url(r'^cart/clear/$', 'clear_cart_viewset', name='clear_cart_api'),
    ....
)

Solution 2

You seem to miss the point of how a class-based view's flow works.

  • As a class-based view is a class (obviously), and Django expects an unbound function (not attached to a class or instance) as a view, as_view handles that by creating an unbound function, and in that function instantiating the class-based view.
  • as_view then calls self.dispatch(request, *args, **kwargs), where self is the instantiated object that's just created.
  • dispatch calls either self.get(request, *args, **kwargs) or self.post(request, *args, **kwargs), depending on the request method (or put, patch or delete if those are allowed and used).

There's no room for a custom function like your clear function, unless you override one of these methods to call self.clear(request). The equivalent of @api_view(['POST']) would be to override the post(request, *args, **kwargs) method:

# views.py
class CartAPIView(APIView):
    def post(self, request, *args, **kwargs):
        # Why would you call this 'queryset'? It's a single object.
        cart = Cart.objects.get(user=request.user)
        cart.clear_cart()

        serializer = CartSerializer(cart)
        return Response(serializer.data, status=status.HTTP_200_OK)

# urls.py
urlpatterns = patterns('app.views',
    url(r'^cart/clear/$', CartAPIView.as_view(), name='clear_cart_api'),
)

Solution 3

It seems what you are looking for is Function Based Views, where you can decorate a function with @api_view()

from rest_framework.decorators import api_view

@api_view(['POST'])
def clear(request):
    """Clear the users cart."""
    queryset = Cart.objects.get(user=request.user)

    queryset.clear_cart()

    serializer = CartSerializer(queryset)

    return Response(serializer.data, status=status.HTTP_200_OK)


# urls.py
urlpatterns = patterns('app.views',
    ....
    url(r'^cart/clear/$', 'clear', name='clear_cart_api'),
    ....
)
Share:
21,045
JDWardle
Author by

JDWardle

Updated on July 09, 2022

Comments

  • JDWardle
    JDWardle almost 2 years

    In Django Rest Framework is it possible to have a custom method in an APIView class be called similar to how .get() or .post() would be called.

    I know it's possible with routers using the @action() or @link() decorators, I'm trying to figure out if you can do something similar to an APIView but have it so I can set the method to whatever url I want.

    I've tried decorating the class with @action() and @api_view() but nothing seems to have worked.

    I'm not exactly sure what I should put in the url for an endpoint to actually call the method in the class. Would I use CartAPIView.clear.as_view(), CartAPIView.clear, or CartAPIView.clear(). I've tried different combinations of calls to CartAPIView but nothing has worked.

    Here's an example of what I'm trying to do:

    # views.py
    class CartAPIView(APIView):
        @api_view(['POST'])
        def clear(self, request):
            """Clear the users cart."""
            queryset = Cart.objects.get(user=request.user)
    
            queryset.clear_cart()
    
            serializer = CartSerializer(queryset)
    
            return Response(serializer.data, status=status.HTTP_200_OK)
    
    # urls.py
    urlpatterns = patterns('app.views',
        ....
        url(r'^cart/clear/$', CartAPIView.clear.as_view(), name='clear_cart_api'),
        ....
    )
    

    Any help would be appreciated.