This commit is contained in:
Tom Christie 2013-04-04 22:24:30 +01:00
parent fd3f538e9f
commit 371698331c
5 changed files with 49 additions and 24 deletions

View File

@ -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. 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 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 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. 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]. Should be mixed in with [SingleObjectAPIView].
## DestroyModelMixin ## DestroyModelMixin

View File

@ -19,6 +19,8 @@ First of all let's refactor our `UserListView` and `UserDetailView` views into a
queryset = User.objects.all() queryset = User.objects.all()
serializer_class = UserSerializer 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. 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 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`, This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions. `update` and `destroy` actions.
Additionally we provide an extra `highlight` action, by using the Additionally we also provide an extra `highlight` action.
`@link` decorator.
""" """
queryset = Snippet.objects.all() queryset = Snippet.objects.all()
serializer_class = SnippetSerializer serializer_class = SnippetSerializer
@ -45,25 +46,40 @@ Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighl
def pre_save(self, obj): def pre_save(self, obj):
obj.owner = self.request.user obj.owner = self.request.user
Notice that we've used the `@link` decorator for the `highlight` method. This time we've used the `ModelViewSet` class in order to get the complete set of default read and write operations.
This decorator can be used to add custom endpoints, other than the standard `create`/`update`/`delete` endpoints.
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 ## Binding ViewSets to URLs explicitly
The handler methods only get bound to the actions when we define the URLConf. 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. 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 from snippets.resources import SnippetResource, UserResource
snippet_list = SnippetViewSet.as_view({'get': 'list', 'post': 'create'}) snippet_list = SnippetViewSet.as_view({
snippet_detail = SnippetViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'}) 'get': 'list',
snippet_highlight = SnippetViewSet.as_view({'get': 'highlight'}) 'post': 'create'
user_list = UserViewSet.as_view({'get': 'list', 'post': 'create'}) })
user_detail = UserViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'}) 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. 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 ## 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. 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. # Create a router and register our views and view sets with it.
router = DefaultRouter() router = DefaultRouter()
router.register(r'^/', views.api_root) router.register(r'^/$', views.api_root)
router.register(r'^snippets/', views.SnippetViewSet, 'snippet') router.register(r'^snippets/', views.SnippetViewSet, 'snippet')
router.register(r'^users/', views.UserViewSet, 'user') router.register(r'^users/', views.UserViewSet, 'user')
# The urlconf is determined automatically by the router. # The urlconf is determined automatically by the router.
urlpatterns = router.urlpatterns 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) urlpatterns = format_suffix_patterns(urlpatterns)
## Trade-offs between views vs viewsets. ## Trade-offs between views vs viewsets.

View File

@ -187,8 +187,7 @@ class UpdateAPIView(mixins.UpdateModelMixin,
return self.update(request, *args, **kwargs) return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs): def patch(self, request, *args, **kwargs):
kwargs['partial'] = True return self.partial_update(request, *args, **kwargs)
return self.update(request, *args, **kwargs)
class ListCreateAPIView(mixins.ListModelMixin, class ListCreateAPIView(mixins.ListModelMixin,
@ -217,8 +216,7 @@ class RetrieveUpdateAPIView(mixins.RetrieveModelMixin,
return self.update(request, *args, **kwargs) return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs): def patch(self, request, *args, **kwargs):
kwargs['partial'] = True return self.partial_update(request, *args, **kwargs)
return self.update(request, *args, **kwargs)
class RetrieveDestroyAPIView(mixins.RetrieveModelMixin, class RetrieveDestroyAPIView(mixins.RetrieveModelMixin,
@ -248,8 +246,7 @@ class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
return self.update(request, *args, **kwargs) return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs): def patch(self, request, *args, **kwargs):
kwargs['partial'] = True return self.partial_update(request, *args, **kwargs)
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs) return self.destroy(request, *args, **kwargs)

View File

@ -137,6 +137,10 @@ class UpdateModelMixin(object):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 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): def pre_save(self, obj):
""" """
Set any attributes on the object that are implicit in the request. Set any attributes on the object that are implicit in the request.

View File

@ -20,8 +20,16 @@ class BaseRouter(object):
class DefaultRouter(BaseRouter): class DefaultRouter(BaseRouter):
route_list = [ route_list = [
(r'$', {'get': 'list', 'post': 'create'}, 'list'), (r'$', {
(r'(?P<pk>[^/]+)/$', {'get': 'retrieve', 'put': 'update', 'delete': 'destroy'}, 'detail'), 'get': 'list',
'post': 'create'
}, 'list'),
(r'(?P<pk>[^/]+)/$', {
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
}, 'detail'),
] ]
extra_routes = r'(?P<pk>[^/]+)/%s/$' extra_routes = r'(?P<pk>[^/]+)/%s/$'
name_format = '%s-%s' name_format = '%s-%s'