Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nesting ViewSet routes in DRF

I've created 2 ModelViewSets like so (simplified for demonstration):

class SomeBaseViewSet(viewsets.ReadOnlyModelViewSet):
    serializer_class = SomeEventSerializer
    permission_classes = (permissions.IsAuthenticated,)

    def get_queryset(self):
        return SomeObjects.objects.filter(owner=self.request.user)

    def get_serializer_context(self):
        context = super(SomeBaseViewSet, self).get_serializer_context()
        context.update({
            "user": self.request.user,
            "some_context_key": False
        })
        return context

class AdminViewSet(SomeBaseViewSet):
    # Added in the HasAdminPermission
    permission_classes = (permissions.IsAuthenticated, HasAdminPermission)

    # Different queryset filter (overriding `get_queryset` vs setting queryset for standardization)
    def get_queryset(self):
        return SomeObjects.objects.all()

    # The context should have `some_context_key=True`, and `user=request.user`
    def get_serializer_context(self):
        context = super(AdminViewSet, self).get_serializer_context()
        context.update({
            "some_context_key": True
        })
        return context

My router/url config looks like this

router = DefaultRouter()

router.register(r'some_view', SomeBaseViewSet, base_name="some_view")

urlpatterns += [
    url(r'^api/', include(router.urls)),
]

If I wanted to route /api/some_view/admin to the AdminViewSet, what's the best way to do that?

Things I've tried:

  • @list_route on SomeBaseViewSet, but couldn't figure out the "right" way to wire it to my AdminViewSet
  • Adding url(r'^api/some_view/admin$', AdminViewSet.as_view({"get": "list"})) to my urlpatterns (which sorta works but neuters the ViewSet a bit, and is generally really manual anyway):
  • Having a dedicated DefaultRouter for the some_view viewset, which I then mount on url(r'^api/some_view/') - again hacky and pedantic to do with a large number of routes

Is there a "right" way to do what I'm trying to accomplish, or should I reach for a different solution to this problem (i.e. a filter or something)?

I've seen libraries like https://github.com/alanjds/drf-nested-routers, but that seems like overkill for my (fairly simple) needs.

like image 749
Fitblip Avatar asked Nov 06 '16 22:11

Fitblip


People also ask

What is DRF ViewSet?

Django REST framework allows you to combine the logic for a set of related views in a single class, called a ViewSet . In other frameworks you may also find conceptually similar implementations named something like 'Resources' or 'Controllers'.

What are routers in DRF?

Resource routing allows you to quickly declare all of the common routes for a given resourceful controller. Instead of declaring separate routes for your index... a resourceful route declares them in a single line of code.

What is the difference between APIView and ViewSet?

APIView allow us to define functions that match standard HTTP methods like GET, POST, PUT, PATCH, etc. Viewsets allow us to define functions that match to common API object actions like : LIST, CREATE, RETRIEVE, UPDATE, etc.

What is difference between View and ViewSet in django?

While regular views act as handlers for HTTP methods, viewsets give you actions, like create or list . The great thing about viewsets is how they make your code consistent and save you from repetition. Every time you write views that should do more than one thing, a viewset is the thing that you want to go for.


1 Answers

Define your admin viewset using a list route. These params will allow you to perform a get request, with specified permissions(is authenticated and has admin perms), that extends this class. ie /someview/admin or someotherview/admin

from rest_framework.decorators import list_route
class AdminViewSet(viewset.Viewsets):

    @list_route(methods=['get'], 
                  permissions=[permissions.IsAuthenticated, HasAdminPermission],
                  url_path='admin'
     )
     def admin(self, request):
          # All your custom logic in regards to querysets and serializing
          return serialized.data

Then you can extend your any viewset that needs an admin action route.

class SomeBaseViewSet(viewsets.ReadOnlyModelViewSet, AdminViewset):
    serializer_class = SomeEventSerializer
    permission_classes = (permissions.IsAuthenticated,)

    def get_queryset(self):
        return SomeObjects.objects.filter(owner=self.request.user)

    def get_serializer_context(self):
        context = super(SomeBaseViewSet, self).get_serializer_context()
        context.update({
            "user": self.request.user,
            "some_context_key": False
        })
        return context

You want to be careful with this because typically the param after your base route ie /someview/{param}/ is reserved for ID references. Make sure your id reference will not conflict with your detail route.

like image 120
Dap Avatar answered Sep 21 '22 23:09

Dap