From 371698331c979305b5684f864ee6bf5b6d11a44e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 4 Apr 2013 22:24:30 +0100 Subject: [PATCH] Tweaks --- docs/api-guide/generic-views.md | 4 +-- docs/tutorial/6-viewsets-and-routers.md | 44 +++++++++++++++++-------- rest_framework/generics.py | 9 ++--- rest_framework/mixins.py | 4 +++ rest_framework/routers.py | 12 +++++-- 5 files changed, 49 insertions(+), 24 deletions(-) diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 20f1be63a..caf6f53ce 100644 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -208,14 +208,14 @@ Should be mixed in with [SingleObjectAPIView]. Provides a `.update(request, *args, **kwargs)` method, that implements updating and saving an existing model instance. +Also provides a `.partial_update(request, *args, **kwargs)` method, which is similar to the `update` method, except that all fields for the update will be optional. This allows support for HTTP `PATCH` requests. + If an object is updated this returns a `200 OK` response, with a serialized representation of the object as the body of the response. If an object is created, for example when making a `DELETE` request followed by a `PUT` request to the same URL, this returns a `201 Created` response, with a serialized representation of the object as the body of the response. If the request data provided for updating the object was invalid, a `400 Bad Request` response will be returned, with the error details as the body of the response. -A boolean `partial` keyword argument may be supplied to the `.update()` method. If `partial` is set to `True`, all fields for the update will be optional. This allows support for HTTP `PATCH` requests. - Should be mixed in with [SingleObjectAPIView]. ## DestroyModelMixin diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index 8a2108b35..4c1a1abd7 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -19,6 +19,8 @@ First of all let's refactor our `UserListView` and `UserDetailView` views into a queryset = User.objects.all() serializer_class = UserSerializer +Here we've used `ReadOnlyModelViewSet` class to automatically provide the default 'read-only' operations. We're still setting the `queryset` and `serializer_class` attributes exactly as we did when we were using regular views, but we no longer need to provide the same information to two seperate classes. + Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighlight` view classes. We can remove the three views, and again replace them with a single class. from rest_framework import viewsets @@ -29,8 +31,7 @@ Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighl This viewset automatically provides `list`, `create`, `retrieve`, `update` and `destroy` actions. - Additionally we provide an extra `highlight` action, by using the - `@link` decorator. + Additionally we also provide an extra `highlight` action. """ queryset = Snippet.objects.all() serializer_class = SnippetSerializer @@ -45,25 +46,40 @@ Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighl def pre_save(self, obj): obj.owner = self.request.user -Notice that we've used the `@link` decorator for the `highlight` method. -This decorator can be used to add custom endpoints, other than the standard `create`/`update`/`delete` endpoints. +This time we've used the `ModelViewSet` class in order to get the complete set of default read and write operations. -The `@link` decorator will +Notice that we've also used the `@link` decorator to create a custom action, named `highlight`. This decorator can be used to add any custom endpoints that don't fit into the standard `create`/`update`/`delete` style. + +Custom actions which use the `@link` decorator will respond to `GET` requests. We could have instead used the `@action` decorator if we wanted an action that responded to `POST` requests. ## Binding ViewSets to URLs explicitly The handler methods only get bound to the actions when we define the URLConf. To see what's going on under the hood let's first explicitly create a set of views from our ViewSets. -In the `urls.py` file we first need to bind our `ViewSet` classes into a set of concrete views. +In the `urls.py` file we bind our `ViewSet` classes into a set of concrete views. from snippets.resources import SnippetResource, UserResource - snippet_list = SnippetViewSet.as_view({'get': 'list', 'post': 'create'}) - snippet_detail = SnippetViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'}) - snippet_highlight = SnippetViewSet.as_view({'get': 'highlight'}) - user_list = UserViewSet.as_view({'get': 'list', 'post': 'create'}) - user_detail = UserViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'}) + snippet_list = SnippetViewSet.as_view({ + 'get': 'list', + 'post': 'create' + }) + snippet_detail = SnippetViewSet.as_view({ + 'get': 'retrieve', + 'put': 'update', + 'patch': 'partial_update', + 'delete': 'destroy' + }) + snippet_highlight = SnippetViewSet.as_view({ + 'get': 'highlight' + }) + user_list = UserViewSet.as_view({ + 'get': 'list' + }) + user_detail = UserViewSet.as_view({ + 'get': 'retrieve' + }) Notice how we're creating multiple views from each `ViewSet` class, by binding the http methods to the required action for each view. @@ -80,7 +96,7 @@ Now that we've bound our resources into concrete views, that we can register the ## Using Routers -Now that we're using `ViewSet` classes rather than `View` classes, we actually don't need to design the URL conf ourselves. The conventions for wiring up resources into views and urls can be handled automatically, using a `Router` class. All we need to do is register the appropriate view sets with a router, and let it do the rest. +Because we're using `ViewSet` classes rather than `View` classes, we actually don't need to design the URL conf ourselves. The conventions for wiring up resources into views and urls can be handled automatically, using a `Router` class. All we need to do is register the appropriate view sets with a router, and let it do the rest. Here's our re-wired `urls.py` file. @@ -89,14 +105,14 @@ Here's our re-wired `urls.py` file. # Create a router and register our views and view sets with it. router = DefaultRouter() - router.register(r'^/', views.api_root) + router.register(r'^/$', views.api_root) router.register(r'^snippets/', views.SnippetViewSet, 'snippet') router.register(r'^users/', views.UserViewSet, 'user') # The urlconf is determined automatically by the router. urlpatterns = router.urlpatterns - # Add format suffixes to all our URL patterns. + # We can still add format suffixes to all our URL patterns. urlpatterns = format_suffix_patterns(urlpatterns) ## Trade-offs between views vs viewsets. diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 36ecf9150..dea980a56 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -187,8 +187,7 @@ class UpdateAPIView(mixins.UpdateModelMixin, return self.update(request, *args, **kwargs) def patch(self, request, *args, **kwargs): - kwargs['partial'] = True - return self.update(request, *args, **kwargs) + return self.partial_update(request, *args, **kwargs) class ListCreateAPIView(mixins.ListModelMixin, @@ -217,8 +216,7 @@ class RetrieveUpdateAPIView(mixins.RetrieveModelMixin, return self.update(request, *args, **kwargs) def patch(self, request, *args, **kwargs): - kwargs['partial'] = True - return self.update(request, *args, **kwargs) + return self.partial_update(request, *args, **kwargs) class RetrieveDestroyAPIView(mixins.RetrieveModelMixin, @@ -248,8 +246,7 @@ class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, return self.update(request, *args, **kwargs) def patch(self, request, *args, **kwargs): - kwargs['partial'] = True - return self.update(request, *args, **kwargs) + return self.partial_update(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.destroy(request, *args, **kwargs) diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index 7d9a6e654..c700602e8 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -137,6 +137,10 @@ class UpdateModelMixin(object): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + def partial_update(self, request, *args, **kwargs): + kwargs['partial'] = True + return self.update(request, *args, **kwargs) + def pre_save(self, obj): """ Set any attributes on the object that are implicit in the request. diff --git a/rest_framework/routers.py b/rest_framework/routers.py index c37909ff7..afc51f3bb 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -20,8 +20,16 @@ class BaseRouter(object): class DefaultRouter(BaseRouter): route_list = [ - (r'$', {'get': 'list', 'post': 'create'}, 'list'), - (r'(?P[^/]+)/$', {'get': 'retrieve', 'put': 'update', 'delete': 'destroy'}, 'detail'), + (r'$', { + 'get': 'list', + 'post': 'create' + }, 'list'), + (r'(?P[^/]+)/$', { + 'get': 'retrieve', + 'put': 'update', + 'patch': 'partial_update', + 'delete': 'destroy' + }, 'detail'), ] extra_routes = r'(?P[^/]+)/%s/$' name_format = '%s-%s'