From ec076a00786c6b89a55b6ffe2556bb3b777100f5 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sun, 31 Mar 2013 11:36:58 +0100 Subject: Add viewsets/routers to indexs etc --- docs/tutorial/6-viewsets-and-routers.md | 94 +++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 docs/tutorial/6-viewsets-and-routers.md (limited to 'docs/tutorial/6-viewsets-and-routers.md') diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md new file mode 100644 index 00000000..9c8a218f --- /dev/null +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -0,0 +1,94 @@ +# Tutorial 6 - ViewSets & Routers + +REST framework includes an abstraction for dealing with `ViewSets`, that allows the developer to concentrate on modelling the state and interactions of the API, and leave the URL construction to be handled automatically, based on common conventions. + +`ViewSet` classes are almost the same thing as `View` classes, except that they provide operations such as `read`, or `update`, and not method handlers such as `get` or `put`. + +A `ViewSet` class is only bound to a set of method handlers at the last moment, when it is instantiated into a set of views, typically by using a `Router` class which handles the complexities of defining the URL conf for you. + +## Refactoring to use ViewSets + +Let's take our current set of views, and refactor them into view sets. + +First of all let's refactor our `UserListView` and `UserDetailView` views into a single `UserViewSet`. We can remove the two views, and replace then with a single class: + + class UserViewSet(viewsets.ModelViewSet): + queryset = User.objects.all() + serializer_class = UserSerializer + +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. + + class SnippetViewSet(viewsets.ModelViewSet): + queryset = Snippet.objects.all() + serializer_class = SnippetSerializer + permission_classes = (permissions.IsAuthenticatedOrReadOnly, + IsOwnerOrReadOnly,) + + @link(renderer_classes=[renderers.StaticHTMLRenderer]) + def highlight(self, request, *args, **kwargs): + snippet = self.get_object() + return Response(snippet.highlighted) + + 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. + +The `@link` decorator will + +## 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. + + 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'}) + +Notice how we're creating multiple views from each `ViewSet` class, by binding the http methods to the required action for each view. + +Now that we've bound our resources into concrete views, that we can register the views with the URL conf as usual. + + urlpatterns = format_suffix_patterns(patterns('snippets.views', + url(r'^$', 'api_root'), + url(r'^snippets/$', snippet_list, name='snippet-list'), + url(r'^snippets/(?P[0-9]+)/$', snippet_detail, name='snippet-detail'), + url(r'^snippets/(?P[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'), + url(r'^users/$', user_list, name='user-list'), + url(r'^users/(?P[0-9]+)/$', user_detail, name='user-detail') + )) + +## 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. + +Here's our re-wired `urls.py` file. + + from snippets import views + from rest_framework.routers import DefaultRouter + + # Create a router and register our views and view sets with it. + router = DefaultRouter() + 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. + urlpatterns = format_suffix_patterns(urlpatterns) + +## Trade-offs between views vs viewsets. + +Using view sets can be a really useful abstraction. It helps ensure that URL conventions will be consistent across your API, minimises the amount of code you need to write, and allows you to concentrate on the interactions and representations your API provides rather than the specifics of the URL conf. + +That doesn't mean it's always the right approach to take. There's a similar set of trade-offs to consider as when using class-based views instead of function based views. Using view sets is less explicit than building your views individually. + -- cgit v1.2.3 From fb41d2ac8f495ae0728e3f38c6a21306f0507316 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 4 Apr 2013 20:35:40 +0100 Subject: Add support for action and link routing --- docs/tutorial/6-viewsets-and-routers.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'docs/tutorial/6-viewsets-and-routers.md') diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index 9c8a218f..8a2108b3 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -12,13 +12,26 @@ Let's take our current set of views, and refactor them into view sets. First of all let's refactor our `UserListView` and `UserDetailView` views into a single `UserViewSet`. We can remove the two views, and replace then with a single class: - class UserViewSet(viewsets.ModelViewSet): + class UserViewSet(viewsets.ReadOnlyModelViewSet): + """ + This viewset automatically provides `list` and `detail` actions. + """ queryset = User.objects.all() serializer_class = UserSerializer 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.decorators import link + class SnippetViewSet(viewsets.ModelViewSet): + """ + This viewset automatically provides `list`, `create`, `retrieve`, + `update` and `destroy` actions. + + Additionally we provide an extra `highlight` action, by using the + `@link` decorator. + """ queryset = Snippet.objects.all() serializer_class = SnippetSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly, -- cgit v1.2.3 From 371698331c979305b5684f864ee6bf5b6d11a44e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 4 Apr 2013 22:24:30 +0100 Subject: Tweaks --- docs/tutorial/6-viewsets-and-routers.md | 44 ++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 14 deletions(-) (limited to 'docs/tutorial/6-viewsets-and-routers.md') diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index 8a2108b3..4c1a1abd 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. + +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. -The `@link` decorator will +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. -- cgit v1.2.3