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.
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

View File

@ -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.

View File

@ -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)

View File

@ -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.

View File

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