when to map http methods to view methods django rest framework
Asked Answered
W

1

7

I have seen viewsets implemented like this:

can we expand on this line of code in the django rest frameworks docs:

"If we need to, we can bind this viewset into two separate views, like so:"

user_list = UserViewSet.as_view({'get': 'list'})
user_detail = UserViewSet.as_view({'get': 'retrieve'})

where would user_list and user_detail be connected to / used?

more so when would you map the http methods while using viewsets or generic views?

Because I have seen examples like this for view sets not using the mapping and using them. Example of using can we talk about how this works and how it is connected?

task_list = TaskViewSet.as_view({
    'get': 'list',
    'post': 'create'
})

task_detail = TaskViewSet.as_view({
    'get': 'retrieve',
    'put': 'update',
    'patch': 'partial_update',
    'delete': 'destroy'
})

task_router = DefaultRouter()
task_router.register(r'tasks', TaskViewSet)

and also what is up with this:

@detail_route(methods=['post'])
    def set_password(self, request, pk=None):

if we have the route decorators why do we have in url mapping? What is the difference between them?

Wheezy answered 20/7, 2017 at 4:52 Comment(0)
V
11

For your first question, about connecting the UserViewSet, you would use those in your urls file like this:

urlpatterns = [
    url(
        r'^users/$',
        UserViewSet.as_view({'get': 'list'}),
        name='user-list',
    ),
    url(
        r'^users/(?P<pk>\d+)/$',
        UserViewSet.as_view({'get': 'retrieve'}),
        name='user-detail',
    ),
]

This is a read-only implementation of your User model using ViewSets. If you want to list all the users you can request /users/, and if you want to get the user with an id of 1, you would request /users/1/.

What you should understand here is that there are actions which act on your Model, such as listing users or creating a new user, and actions which act on an instance of your Model. For example, if you want to retrieve, update, or delete a User instance, you'd need to include the users primary key in the URL so you can fetch that user.

Your TaskViewSet is not read-only, so let's take a look at what that class should look like.

class TaskViewSet(ModelViewSet):
    queryset = Task
    serializer_class = TaskSerializer
    lookup_field = 'pk'  # This is the default
    lookup_url_kwarg = 'pk'  # This is the default

That is a simple, generic ModelViewSet that you could implement in your urls file like this:

urlpatterns = [
    url(
        r'^tasks/$',
        TaskViewSet.as_view({'get': 'list', 'post': 'create'}),
        name='task-list',
    ),
    url(
        r'^tasks/(?P<pk>\d+)/$',
        TaskViewSet.as_view({'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'}),
        name='task-detail',
    ),
]

Now you can do anything with your Model, you can list, create, retrieve, update, and delete objects. You'll see there's a bit of a pattern forming here using the ModelViewSet, this is where the convenience of routers stands out. Django Rest Frameworks router implementation essentially works by taking a path and a ViewSet and then constructing the urls.

In this example we are using SimpleRouter to generate our url patterns.

router = SimpleRouter()
router.register('users', UserViewSet)  # UserViewSet is ReadOnlyModelViewSet
router.register('tasks', TaskViewSet)  # TaskViewSet is ModelViewSet
urlpatterns = [] + router.urls

Which would generate:

urlpatterns = [
    url(
        r'^users/$',
        UserViewSet.as_view({'get': 'list'}),
        name='user-list',
    ),
    url(
        r'^users/(?P<pk>\d+)/$',
        UserViewSet.as_view({'get': 'retrieve'}),
        name='user-detail',
    ),
    url(
        r'^tasks/$',
        TaskViewSet.as_view({'get': 'list', 'post': 'create'}),
        name='task-list',
    ),
    url(
        r'^tasks/(?P<pk>\d+)/$',
        TaskViewSet.as_view({'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'}),
        name='task-detail',
    ),
]

I hope this makes sense so far, and you can see how you might use these classes to reduce how much code you'll need to write.

Now I'll explain what the @detail_route and @list_route decorators are doing. These decorators help the Router classes register custom methods on your ViewSet.

For rest framework 3.8 and above @detail_route and @list_route have been deprecated in favour of the @action decorator Replace @detail_route(...) with @action(detail=True, ...). Replace @list_route(...) uses with @action(detail=False, ...).

You should use the @list_route decorator for actions that make sense to be called upon the Model and not an instance of the model, an example of this might be if you wanted to provide some endpoint to download a report on your model as csv. You should use the @detail_route decorator for actions that happen for an instance of your Model. I'm going to extend the TaskViewSet example from before.

class TaskViewSet(ModelViewSet):
    queryset = Task
    serializer_class = TaskSerializer
    lookup_field = 'pk'  # This is the default
    lookup_url_kwarg = 'pk'  # This is the default

    @list_route(methods=['get'])
    def download(self, request, *args, **kwargs):
        """Download queryset as xlsx"""
        qs = self.get_queryset()
        return queryset_to_excel(qs)  # simple example

    @detail_route(methods=['get'])
    def details(self, request, *args, **kwargs):
        """Return intricate details of Task"""
        object = self.get_object()
        return object.get_intricate_task_details()

If we use this TaskViewSet with a router:

router = SimpleRouter()
router.register('tasks', TaskViewSet)
urlpatterns = [] + router.urls

I've added a download method on the list, to download the queryset as an Excel file, and I've added a details method to the detail, which will return some extra information that may be expensive to retrieve, so we don't want it on the normal detail response. Then we will get a url configuration like:

urlpatterns = [
    url(
        r'^tasks/$',
        TaskViewSet.as_view({'get': 'list', 'post': 'create'}),
        name='task-list',
    ),
    url(
        r'^tasks/download/$',
        TaskViewSet.as_view({'get': 'download'}),
        name='task-download',
    ),
    url(
        r'^tasks/(?P<pk>\d+)/$',
        TaskViewSet.as_view({'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'}),
        name='task-detail',
    ),
    url(
        r'^tasks/(?P<pk>\d+)/details/$',
        TaskViewSet.as_view({'get': 'detail'}),
        name='task-details',
    ),
]

The router has now generated additional routes for the custom methods defined on the TaskViewSet.

I would recommend reading up on rest frameworks documentation on ViewSet and SimpleRouter.

http://www.django-rest-framework.org/api-guide/viewsets/#modelviewset
http://www.django-rest-framework.org/api-guide/routers/#simplerouter

View answered 20/7, 2017 at 5:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.