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
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):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 routesIs 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.
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'.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With