Search code examples
pythondjangodjango-rest-frameworkdjango-views

Django DRF use same URL pattern for different views


In Django Rest Framework, I have two separate views for the same model. I would like to use the same url pattern to access both views, and differentiate between the two based on the method that is used. So I have something like:

class MyObjectListAPIView(generics.ListAPIView):
    pass


class MyObjectCreateAPIView(generics.CreateAPIView):
    pass

Both views obviously would have different logic. The url pattern for both would be 'myObjects\', and depending on the method that is used (GET or POST), it would need to refer to the appropriate view (MyObjectListAPIView or MyObjectCreateAPIView respectively). Is there a way to achieve this (without reverting to plain Django and losing the functionality of DRF)?


Solution

  • Short Answer:

    The out-of-the-box way to achieve this is with ViewSets, specifically the ModelViewSet, in conjunction with the default ModelSerializer and DRF's built-in DefaultRouter. It would look like the below:

    ## views.py ##
    from rest_framework import viewsets
    
    from myapp.models import MyModel
    from myapp.serializers import MyModelSerializer
    
    class MyObjectViewSet(viewsets.ModelViewSet):
        queryset = MyModel.objects.all()
        serializer_class = MyModelSerializer
    
    
    ## urls.py ##
    from django.urls import path, include
    from rest_framework.routers import DefaultRouter
    
    from .views import MyObjectViewSet
    
    router = DefaultRouter()
    router.register(r'myObjects', MyObjectViewSet)
    
    urlpatterns = [
        path('', include(router.urls)),
    ]
    

    Note, this will achieve what you want, i.e. same endpoint 'myObjects/' with different logic based on HTTP Method (GET -> List, POST -> Create) -- BUT note that it will also do more than just this, as you will also get the associated Retrieve/Update/Delete functionality at the endpoint 'myObjects/<pk>/'.


    Longer Answer:

    If you want to avoid ViewSets for any particular reason, you would just use the GenericAPIView with the ListModelMixin and CreateModelMixin as well. This could look something like the below:

    ## views.py ##
    from rest_framework import generics, mixins
    from myapp.models import MyModel
    from myapp.serializers import MyModelSerializer
    
    class MyObjectListCreateAPIView(mixins.ListModelMixin,
                                    mixins.CreateModelMixin,
                                    generics.GenericAPIView):
        queryset = MyModel.objects.all()
        serializer_class = MyModelSerializer
    
        def get(self, request, *args, **kwargs):
            return self.list(request, *args, **kwargs)
    
        def post(self, request, *args, **kwargs):
            return self.create(request, *args, **kwargs)
    

    Then simply register it to your URLs file as you normally would for any Class-Based View, with no need for the Router here; e.g. like the below:

    ## urls.py ##
    from django.urls import path
    
    from .views import MyObjectListCreateAPIView
    
    urlpatterns = [
        # ..any other url patterns.. #
        path('myObjects/', MyObjectListCreateAPIView.as_view())
    ]
    

    This should satisfy your constraints exactly, without the additional features the ViewSet offers.

    The official DRF docs do a very good step-by-step tutorial on how to move from 'APIView' to 'GenericViews' plus 'Mixins', all the way through to 'ViewSets with Routers', and the docs are pretty comprehensive. I recommend taking a look at it here if you haven't already.