From 8f3931e02d0f0ba803075ca65dc8617ee959456f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 15 Jan 2013 17:50:39 +0000 Subject: Update docs --- docs/tutorial/6-resource-orientated-projects.md | 119 +++++++++++++++--------- 1 file changed, 76 insertions(+), 43 deletions(-) (limited to 'docs/tutorial') diff --git a/docs/tutorial/6-resource-orientated-projects.md b/docs/tutorial/6-resource-orientated-projects.md index 9ee599ae..97fb5d69 100644 --- a/docs/tutorial/6-resource-orientated-projects.md +++ b/docs/tutorial/6-resource-orientated-projects.md @@ -1,49 +1,93 @@ # Tutorial 6 - Resources -Resource classes are just View classes that don't have any handler methods bound to them. The actions on a resource are defined, +REST framework includes an abstraction for dealing with resources, 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. -This allows us to: +To work with resources, we can use either the `Resource` class, which does not define any default handlers, or the `ModelResource` class, which provides a default set of CRUD operations. -* Encapsulate common behaviour across a class of views, in a single Resource class. -* Separate out the actions of a Resource from the specifics of how those actions should be bound to a particular set of URLs. +Resource classes are very similar to class based views, except that they provide operations such as `read`, or `update`, and not HTTP method handlers such as `get` or `put`. Resources are only bound to HTTP method handlers at the last moment, when they are instantiated into views, typically by using a `Router` class which handles the complexities of defining the URL conf for you. -## Refactoring to use Resources, not Views +## Refactoring to use Resources, instead of Views -For instance, we can re-write our 4 sets of views into something more compact... +Let's take our current set of views, and refactor them into resources. +We'll remove our existing `views.py` module, and instead create a `resources.py` -resources.py +Our `UserResource` is simple, since we just want the default model CRUD behavior, so we inherit from `ModelResource` and include the same set of attributes we used for the corresponding view classes. - class BlogPostResource(ModelResource): - serializer_class = BlogPostSerializer - model = BlogPost - permissions_classes = (permissions.IsAuthenticatedOrReadOnly,) - throttle_classes = (throttles.UserRateThrottle,) + class UserResource(resources.ModelResource): + model = User + serializer_class = UserSerializer + +There's a little bit more work to do for the `SnippetResource`. Again, we want the +default set of CRUD behavior, but we also want to include an endpoint for snippet highlights. + + class SnippetResource(resources.ModelResource): + model = Snippet + 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` endpoint. This decorator can be used for non-CRUD endpoints that are "safe" operations that do not change server state. Using `@link` indicates that we want to use a `GET` method for these operations. For non-CRUD operations we can also use the `@action` decorator for any operations that change server state, which ensures that the `POST` method will be used for the operation. - class CommentResource(ModelResource): - serializer_class = CommentSerializer - model = Comment - permissions_classes = (permissions.IsAuthenticatedOrReadOnly,) - throttle_classes = (throttles.UserRateThrottle,) ## Binding Resources to URLs explicitly -The handler methods only get bound to the actions when we define the URLConf. Here's our urls.py: - comment_root = CommentResource.as_view(actions={ +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 resources. + +In the `urls.py` file we first need to bind our resources to concrete views. + + snippet_list = SnippetResource.as_view(actions={ + 'get': 'list', + 'post': 'create' + }) + snippet_detail = SnippetResource.as_view(actions={ + 'get': 'retrieve', + 'put': 'update', + 'delete': 'destroy' + }) + snippet_highlight = SnippetResource.as_view(actions={ + 'get': 'highlight' + }) + user_list = UserResource.as_view(actions={ 'get': 'list', 'post': 'create' }) - comment_instance = CommentInstance.as_view(actions={ + user_detail = UserResource.as_view(actions={ 'get': 'retrieve', 'put': 'update', 'delete': 'destroy' }) - ... # And for blog post - - urlpatterns = patterns('blogpost.views', - url(r'^$', comment_root), - url(r'^(?P[0-9]+)$', comment_instance) - ... # And for blog post - ) + +We've now got a set of views exactly as we did before, that we can register with the URL conf. + +Replace the remainder of the `urls.py` file with the following: + + 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 @@ -52,25 +96,14 @@ Right now that hasn't really saved us a lot of code. However, now that we're us from blog import resources from rest_framework.routers import DefaultRouter - router = DefaultRouter() - router.register(resources.BlogPostResource) - router.register(resources.CommentResource) + router = DefaultRouter(include_root=True, include_format_suffixes=True) + router.register(resources.SnippetResource) + router.register(resources.UserResource) urlpatterns = router.urlpatterns ## Trade-offs between views vs resources. -Writing resource-oriented code can be a good thing. It helps ensure that URL conventions will be consistent across your APIs, and minimises the amount of code you need to write. - -The trade-off is that the behaviour is less explict. It can be more difficult to determine what code path is being followed, or where to override some behaviour. - -## Onwards and upwards. - -We've reached the end of our tutorial. If you want to get more involved in the REST framework project, here's a few places you can start: - -* Contribute on GitHub by reviewing issues, and submitting issues or pull requests. -* Join the REST framework group, and help build the community. -* Follow me [on Twitter][twitter] and say hi. +Writing resource-oriented code can be a good thing. It helps ensure that URL conventions will be consistent across your APIs, 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. -**Now go build some awesome things.** +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. Using resources is less explicit than building your views individually. -[twitter]: https://twitter.com/_tomchristie -- cgit v1.2.3 From 4a7139e41d2500776c30e663c1cebce74b49270d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 15 Jan 2013 21:49:24 +0000 Subject: Tweaks --- docs/tutorial/6-resource-orientated-projects.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'docs/tutorial') diff --git a/docs/tutorial/6-resource-orientated-projects.md b/docs/tutorial/6-resource-orientated-projects.md index 97fb5d69..019371d7 100644 --- a/docs/tutorial/6-resource-orientated-projects.md +++ b/docs/tutorial/6-resource-orientated-projects.md @@ -44,23 +44,25 @@ To see what's going on under the hood let's first explicitly create a set of vie In the `urls.py` file we first need to bind our resources to concrete views. - snippet_list = SnippetResource.as_view(actions={ + from snippets import resources + + snippet_list = resources.SnippetResource.as_view({ 'get': 'list', 'post': 'create' }) - snippet_detail = SnippetResource.as_view(actions={ + snippet_detail = resources.SnippetResource.as_view({ 'get': 'retrieve', 'put': 'update', 'delete': 'destroy' }) - snippet_highlight = SnippetResource.as_view(actions={ + snippet_highlight = resources.SnippetResource.as_view({ 'get': 'highlight' }) - user_list = UserResource.as_view(actions={ + user_list = resources.UserResource.as_view({ 'get': 'list', 'post': 'create' }) - user_detail = UserResource.as_view(actions={ + user_detail = resources.UserResource.as_view({ 'get': 'retrieve', 'put': 'update', 'delete': 'destroy' @@ -93,12 +95,12 @@ Replace the remainder of the `urls.py` file with the following: Right now that hasn't really saved us a lot of code. However, now that we're using Resources rather than Views, we actually don't need to design the urlconf ourselves. The conventions for wiring up resources into views and urls can be handled automatically, using `Router` classes. All we need to do is register the appropriate resources with a router, and let it do the rest. Here's our re-wired `urls.py` file. - from blog import resources + from snippets import resources from rest_framework.routers import DefaultRouter - router = DefaultRouter(include_root=True, include_format_suffixes=True) - router.register(resources.SnippetResource) - router.register(resources.UserResource) + router = DefaultRouter() + router.register('snippets', resources.SnippetResource) + router.register('users', resources.UserResource) urlpatterns = router.urlpatterns ## Trade-offs between views vs resources. -- cgit v1.2.3 From 4c6396108704d38f534a16577de59178b1d0df3b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 17 Jan 2013 12:30:28 +0000 Subject: Tweak resource docs --- docs/tutorial/6-resource-orientated-projects.md | 66 ++++++++----------------- 1 file changed, 21 insertions(+), 45 deletions(-) (limited to 'docs/tutorial') diff --git a/docs/tutorial/6-resource-orientated-projects.md b/docs/tutorial/6-resource-orientated-projects.md index 019371d7..37ab1419 100644 --- a/docs/tutorial/6-resource-orientated-projects.md +++ b/docs/tutorial/6-resource-orientated-projects.md @@ -42,65 +42,41 @@ Notice that we've used the `@link` decorator for the `highlight` endpoint. This 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 resources. -In the `urls.py` file we first need to bind our resources to concrete views. +In the `urls.py` file we first need to bind our resource classes into a set of concrete views. - from snippets import resources + from snippets.resources import SnippetResource, UserResource + + snippet_list = SnippetResource.as_view({'get': 'list', 'post': 'create'}) + snippet_detail = SnippetResource.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'}) + snippet_highlight = SnippetResource.as_view({'get': 'highlight'}) + user_list = UserResource.as_view({'get': 'list', 'post': 'create'}) + user_detail = UserResource.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'}) - snippet_list = resources.SnippetResource.as_view({ - 'get': 'list', - 'post': 'create' - }) - snippet_detail = resources.SnippetResource.as_view({ - 'get': 'retrieve', - 'put': 'update', - 'delete': 'destroy' - }) - snippet_highlight = resources.SnippetResource.as_view({ - 'get': 'highlight' - }) - user_list = resources.UserResource.as_view({ - 'get': 'list', - 'post': 'create' - }) - user_detail = resources.UserResource.as_view({ - 'get': 'retrieve', - 'put': 'update', - 'delete': 'destroy' - }) - -We've now got a set of views exactly as we did before, that we can register with the URL conf. - -Replace the remainder of the `urls.py` file with the following: +Notice how create multiple views onto a single resource 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') + 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 -Right now that hasn't really saved us a lot of code. However, now that we're using Resources rather than Views, we actually don't need to design the urlconf ourselves. The conventions for wiring up resources into views and urls can be handled automatically, using `Router` classes. All we need to do is register the appropriate resources with a router, and let it do the rest. Here's our re-wired `urls.py` file. +Now that we're using Resources rather than Views, 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 `Router` classes. All we need to do is register the appropriate resources with a router, and let it do the rest. + +Here's our re-wired `urls.py` file. from snippets import resources from rest_framework.routers import DefaultRouter router = DefaultRouter() - router.register('snippets', resources.SnippetResource) - router.register('users', resources.UserResource) + router.register(r'^snippets/', resources.SnippetResource, 'snippet') + router.register(r'^users/', resources.UserResource, 'user') urlpatterns = router.urlpatterns ## Trade-offs between views vs resources. -- cgit v1.2.3 From dab158e1fd827285032e331c10acc60e9719ace3 Mon Sep 17 00:00:00 2001 From: Sitong Peng Date: Mon, 25 Mar 2013 09:27:12 -0700 Subject: Tiny typo --- docs/tutorial/4-authentication-and-permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/tutorial') diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md index 3ee755a2..878672bb 100644 --- a/docs/tutorial/4-authentication-and-permissions.md +++ b/docs/tutorial/4-authentication-and-permissions.md @@ -179,7 +179,7 @@ Now, if you open a browser again, you find that the 'DELETE' and 'PUT' actions o ## Authenticating with the API -Because we now have a set of permissions on the API, we need to authenticate our requests to it if we want to edit any snippets. We havn't set up any [authentication classes][authentication], so the defaults are currently applied, which are `SessionAuthentication` and `BasicAuthentication`. +Because we now have a set of permissions on the API, we need to authenticate our requests to it if we want to edit any snippets. We haven't set up any [authentication classes][authentication], so the defaults are currently applied, which are `SessionAuthentication` and `BasicAuthentication`. When we interact with the API through the web browser, we can login, and the browser session will then provide the required authentication for the requests. -- cgit v1.2.3 From 97aa0239163868af40b0a5660c48b54bd7656ad6 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 30 Mar 2013 17:22:52 +0000 Subject: Updating tutorial --- docs/tutorial/6-resource-orientated-projects.md | 69 ++++++++++++++----------- 1 file changed, 38 insertions(+), 31 deletions(-) (limited to 'docs/tutorial') diff --git a/docs/tutorial/6-resource-orientated-projects.md b/docs/tutorial/6-resource-orientated-projects.md index 37ab1419..9c8a218f 100644 --- a/docs/tutorial/6-resource-orientated-projects.md +++ b/docs/tutorial/6-resource-orientated-projects.md @@ -1,27 +1,25 @@ -# Tutorial 6 - Resources +# Tutorial 6 - ViewSets & Routers -REST framework includes an abstraction for dealing with resources, 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. +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. -To work with resources, we can use either the `Resource` class, which does not define any default handlers, or the `ModelResource` class, which provides a default set of CRUD operations. +`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`. -Resource classes are very similar to class based views, except that they provide operations such as `read`, or `update`, and not HTTP method handlers such as `get` or `put`. Resources are only bound to HTTP method handlers at the last moment, when they are instantiated into views, typically by using a `Router` class which handles the complexities of defining the URL conf for you. +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 Resources, instead of Views +## Refactoring to use ViewSets -Let's take our current set of views, and refactor them into resources. -We'll remove our existing `views.py` module, and instead create a `resources.py` +Let's take our current set of views, and refactor them into view sets. -Our `UserResource` is simple, since we just want the default model CRUD behavior, so we inherit from `ModelResource` and include the same set of attributes we used for the corresponding view classes. +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 UserResource(resources.ModelResource): - model = User + class UserViewSet(viewsets.ModelViewSet): + queryset = User.objects.all() serializer_class = UserSerializer -There's a little bit more work to do for the `SnippetResource`. Again, we want the -default set of CRUD behavior, but we also want to include an endpoint for snippet highlights. +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 SnippetResource(resources.ModelResource): - model = Snippet + class SnippetViewSet(viewsets.ModelViewSet): + queryset = Snippet.objects.all() serializer_class = SnippetSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,) @@ -34,25 +32,27 @@ default set of CRUD behavior, but we also want to include an endpoint for snippe def pre_save(self, obj): obj.owner = self.request.user -Notice that we've used the `@link` decorator for the `highlight` endpoint. This decorator can be used for non-CRUD endpoints that are "safe" operations that do not change server state. Using `@link` indicates that we want to use a `GET` method for these operations. For non-CRUD operations we can also use the `@action` decorator for any operations that change server state, which ensures that the `POST` method will be used for the operation. +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 Resources to URLs explicitly +## 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 resources. +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 resource classes into a set of concrete views. +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 = SnippetResource.as_view({'get': 'list', 'post': 'create'}) - snippet_detail = SnippetResource.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'}) - snippet_highlight = SnippetResource.as_view({'get': 'highlight'}) - user_list = UserResource.as_view({'get': 'list', 'post': 'create'}) - user_detail = UserResource.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', '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 create multiple views onto a single resource 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. Now that we've bound our resources into concrete views, that we can register the views with the URL conf as usual. @@ -67,21 +67,28 @@ Now that we've bound our resources into concrete views, that we can register the ## Using Routers -Now that we're using Resources rather than Views, 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 `Router` classes. All we need to do is register the appropriate resources with a router, and let it do the rest. +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 resources + 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'^snippets/', resources.SnippetResource, 'snippet') - router.register(r'^users/', resources.UserResource, 'user') + 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 resources. +## Trade-offs between views vs viewsets. -Writing resource-oriented code can be a good thing. It helps ensure that URL conventions will be consistent across your APIs, 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. +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. Using resources is less explicit than building your views individually. +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 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-resource-orientated-projects.md | 94 ------------------------- docs/tutorial/6-viewsets-and-routers.md | 94 +++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 94 deletions(-) delete mode 100644 docs/tutorial/6-resource-orientated-projects.md create mode 100644 docs/tutorial/6-viewsets-and-routers.md (limited to 'docs/tutorial') diff --git a/docs/tutorial/6-resource-orientated-projects.md b/docs/tutorial/6-resource-orientated-projects.md deleted file mode 100644 index 9c8a218f..00000000 --- a/docs/tutorial/6-resource-orientated-projects.md +++ /dev/null @@ -1,94 +0,0 @@ -# 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. - 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') 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 ad9aa23198bf1008651a778dd3f633570d4ee2d3 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 4 Apr 2013 21:51:41 +0100 Subject: Use consistent header style in tutorial part 5 --- docs/tutorial/5-relationships-and-hyperlinked-apis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/tutorial') diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md index a702a09d..27a10840 100644 --- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md +++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md @@ -1,4 +1,4 @@ -# Tutorial 5 - Relationships & Hyperlinked APIs +# Tutorial 5: Relationships & Hyperlinked APIs At the moment relationships within our API are represented by using primary keys. In this part of the tutorial we'll improve the cohesion and discoverability of our API, by instead using hyperlinking for relationships. -- 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') 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 From b94da2468cdda6b0ad491574d35097d0e336ea7f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 24 Apr 2013 22:40:24 +0100 Subject: Various clean up and lots of docs --- docs/tutorial/3-class-based-views.md | 16 +++---- docs/tutorial/4-authentication-and-permissions.md | 4 +- .../5-relationships-and-hyperlinked-apis.md | 30 +++---------- docs/tutorial/6-viewsets-and-routers.md | 52 +++++++++++++++++----- 4 files changed, 56 insertions(+), 46 deletions(-) (limited to 'docs/tutorial') diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md index e05017c5..70cf2c54 100644 --- a/docs/tutorial/3-class-based-views.md +++ b/docs/tutorial/3-class-based-views.md @@ -92,8 +92,8 @@ Let's take a look at how we can compose our views by using the mixin classes. class SnippetList(mixins.ListModelMixin, mixins.CreateModelMixin, - generics.MultipleObjectAPIView): - model = Snippet + generics.GenericAPIView): + queryset = Snippet.objects.all() serializer_class = SnippetSerializer def get(self, request, *args, **kwargs): @@ -102,15 +102,15 @@ Let's take a look at how we can compose our views by using the mixin classes. def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) -We'll take a moment to examine exactly what's happening here. We're building our view using `MultipleObjectAPIView`, and adding in `ListModelMixin` and `CreateModelMixin`. +We'll take a moment to examine exactly what's happening here. We're building our view using `GenericAPIView`, and adding in `ListModelMixin` and `CreateModelMixin`. The base class provides the core functionality, and the mixin classes provide the `.list()` and `.create()` actions. We're then explicitly binding the `get` and `post` methods to the appropriate actions. Simple enough stuff so far. class SnippetDetail(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, - generics.SingleObjectAPIView): - model = Snippet + generics.GenericAPIView): + queryset = Snippet.objects.all() serializer_class = SnippetSerializer def get(self, request, *args, **kwargs): @@ -122,7 +122,7 @@ The base class provides the core functionality, and the mixin classes provide th def delete(self, request, *args, **kwargs): return self.destroy(request, *args, **kwargs) -Pretty similar. This time we're using the `SingleObjectAPIView` class to provide the core functionality, and adding in mixins to provide the `.retrieve()`, `.update()` and `.destroy()` actions. +Pretty similar. Again we're using the `GenericAPIView` class to provide the core functionality, and adding in mixins to provide the `.retrieve()`, `.update()` and `.destroy()` actions. ## Using generic class based views @@ -134,12 +134,12 @@ Using the mixin classes we've rewritten the views to use slightly less code than class SnippetList(generics.ListCreateAPIView): - model = Snippet + queryset = Snippet.objects.all() serializer_class = SnippetSerializer class SnippetDetail(generics.RetrieveUpdateDestroyAPIView): - model = Snippet + queryset = Snippet.objects.all() serializer_class = SnippetSerializer Wow, that's pretty concise. We've gotten a huge amount for free, and our code looks like good, clean, idiomatic Django. diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md index 878672bb..d3ee8e79 100644 --- a/docs/tutorial/4-authentication-and-permissions.md +++ b/docs/tutorial/4-authentication-and-permissions.md @@ -68,12 +68,12 @@ Because `'snippets'` is a *reverse* relationship on the User model, it will not We'll also add a couple of views. We'd like to just use read-only views for the user representations, so we'll use the `ListAPIView` and `RetrieveAPIView` generic class based views. class UserList(generics.ListAPIView): - model = User + queryset = User.objects.all() serializer_class = UserSerializer class UserDetail(generics.RetrieveAPIView): - model = User + queryset = User.objects.all() serializer_class = UserSerializer Finally we need to add those views into the API, by referencing them from the URL conf. diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md index 27a10840..c9c37547 100644 --- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md +++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md @@ -34,8 +34,8 @@ Instead of using a concrete generic view, we'll use the base class for represent from rest_framework import renderers from rest_framework.response import Response - class SnippetHighlight(generics.SingleObjectAPIView): - model = Snippet + class SnippetHighlight(generics.GenericAPIView): + queryset = Snippet.objects.all() renderer_classes = (renderers.StaticHTMLRenderer,) def get(self, request, *args, **kwargs): @@ -143,34 +143,16 @@ We can change the default list style to use pagination, by modifying our `settin 'PAGINATE_BY': 10 } -Note that settings in REST framework are all namespaced into a single dictionary setting, named 'REST_FRAMEWORK', which helps keep them well seperated from your other project settings. +Note that settings in REST framework are all namespaced into a single dictionary setting, named 'REST_FRAMEWORK', which helps keep them well separated from your other project settings. We could also customize the pagination style if we needed too, but in this case we'll just stick with the default. -## Reviewing our work +## Browsing the API If we open a browser and navigate to the browseable API, you'll find that you can now work your way around the API simply by following links. You'll also be able to see the 'highlight' links on the snippet instances, that will take you to the highlighted code HTML representations. -We've now got a complete pastebin Web API, which is fully web browseable, and comes complete with authentication, per-object permissions, and multiple renderer formats. +In [part 6][tut-6] of the tutorial we'll look at how we can use ViewSets and Routers to reduce the amount of code we need to build our API. -We've walked through each step of the design process, and seen how if we need to customize anything we can gradually work our way down to simply using regular Django views. - -You can review the final [tutorial code][repo] on GitHub, or try out a live example in [the sandbox][sandbox]. - -## Onwards and upwards - -We've reached the end of our tutorial. If you want to get more involved in the REST framework project, here's a few places you can start: - -* Contribute on [GitHub][github] by reviewing and submitting issues, and making pull requests. -* Join the [REST framework discussion group][group], and help build the community. -* Follow [the author][twitter] on Twitter and say hi. - -**Now go build awesome things.** - -[repo]: https://github.com/tomchristie/rest-framework-tutorial -[sandbox]: http://restframework.herokuapp.com/ -[github]: https://github.com/tomchristie/django-rest-framework -[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework -[twitter]: https://twitter.com/_tomchristie +[tut-6]: 6-viewsets-and-routers.md diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index 4c1a1abd..876d89ac 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -1,6 +1,6 @@ # 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. +REST framework includes an abstraction for dealing with `ViewSets`, that allows the developer to concentrate on modeling 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`. @@ -19,7 +19,7 @@ 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. +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 separate 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. @@ -103,21 +103,49 @@ 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. + # Create a router and register our viewsets with it. router = DefaultRouter() - router.register(r'^/$', views.api_root) - router.register(r'^snippets/', views.SnippetViewSet, 'snippet') - router.register(r'^users/', views.UserViewSet, 'user') + 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 - - # We can still add format suffixes to all our URL patterns. - urlpatterns = format_suffix_patterns(urlpatterns) + # The API URLs are now determined automatically by the router. + # Additionally, we include the login URLs for the browseable API. + urlpatterns = patterns('', + url(r'^', include(router.urls)), + url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) + ) + +Registering the viewsets with the router is similar to providing a urlpattern. We include three arguments - the URL prefix for the views, the viewset itself, and the base name that should be used for constructing the URL names, such as `snippet-list`. + +The `DefaultRouter` class we're using also automatically creates the API root view for us, so we can now delete the `api_root` method from our `views` module. ## 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. +Using viewsets can be a really useful abstraction. It helps ensure that URL conventions will be consistent across your API, minimizes 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. +## Reviewing our work + +With an incredibly small amount of code, we've now got a complete pastebin Web API, which is fully web browseable, and comes complete with authentication, per-object permissions, and multiple renderer formats. + +We've walked through each step of the design process, and seen how if we need to customize anything we can gradually work our way down to simply using regular Django views. + +You can review the final [tutorial code][repo] on GitHub, or try out a live example in [the sandbox][sandbox]. + +## Onwards and upwards + +We've reached the end of our tutorial. If you want to get more involved in the REST framework project, here's a few places you can start: + +* Contribute on [GitHub][github] by reviewing and submitting issues, and making pull requests. +* Join the [REST framework discussion group][group], and help build the community. +* Follow [the author][twitter] on Twitter and say hi. + +**Now go build awesome things.** + + +[repo]: https://github.com/tomchristie/rest-framework-tutorial +[sandbox]: http://restframework.herokuapp.com/ +[github]: https://github.com/tomchristie/django-rest-framework +[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework +[twitter]: https://twitter.com/_tomchristie \ No newline at end of file -- cgit v1.2.3 From 8fa79a7fd38dda015afa658084361c6da2856e46 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Apr 2013 14:59:21 +0100 Subject: Deal with List/Instance suffixes for viewsets --- docs/tutorial/6-viewsets-and-routers.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'docs/tutorial') diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index 876d89ac..25a974a1 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -105,8 +105,8 @@ Here's our re-wired `urls.py` file. # Create a router and register our viewsets with it. router = DefaultRouter() - router.register(r'snippets', views.SnippetViewSet, 'snippet') - router.register(r'users', views.UserViewSet, 'user') + router.register(r'snippets', views.SnippetViewSet, name='snippet') + router.register(r'users', views.UserViewSet, name='user') # The API URLs are now determined automatically by the router. # Additionally, we include the login URLs for the browseable API. @@ -148,4 +148,4 @@ We've reached the end of our tutorial. If you want to get more involved in the [sandbox]: http://restframework.herokuapp.com/ [github]: https://github.com/tomchristie/django-rest-framework [group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework -[twitter]: https://twitter.com/_tomchristie \ No newline at end of file +[twitter]: https://twitter.com/_tomchristie -- cgit v1.2.3 From 8cabae22c5330da2e0a15a6d61ef038a6447756a Mon Sep 17 00:00:00 2001 From: Victor Shih Date: Wed, 1 May 2013 21:26:40 -0700 Subject: Example and spelling fixes. Change "browseable" to "browsable" for consistency. --- docs/tutorial/2-requests-and-responses.md | 8 ++++---- docs/tutorial/4-authentication-and-permissions.md | 8 ++++---- docs/tutorial/5-relationships-and-hyperlinked-apis.md | 4 ++-- docs/tutorial/quickstart.md | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) (limited to 'docs/tutorial') diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md index 63cee3a6..3a002cb0 100644 --- a/docs/tutorial/2-requests-and-responses.md +++ b/docs/tutorial/2-requests-and-responses.md @@ -140,7 +140,7 @@ We can control the format of the response that we get back, either by using the Or by appending a format suffix: curl http://127.0.0.1:8000/snippets/.json # JSON suffix - curl http://127.0.0.1:8000/snippets/.api # Browseable API suffix + curl http://127.0.0.1:8000/snippets/.api # Browsable API suffix Similarly, we can control the format of the request that we send, using the `Content-Type` header. @@ -160,9 +160,9 @@ Now go and open the API in a web browser, by visiting [http://127.0.0.1:8000/sni Because the API chooses the content type of the response based on the client request, it will, by default, return an HTML-formatted representation of the resource when that resource is requested by a web browser. This allows for the API to return a fully web-browsable HTML representation. -Having a web-browseable API is a huge usability win, and makes developing and using your API much easier. It also dramatically lowers the barrier-to-entry for other developers wanting to inspect and work with your API. +Having a web-browsable API is a huge usability win, and makes developing and using your API much easier. It also dramatically lowers the barrier-to-entry for other developers wanting to inspect and work with your API. -See the [browsable api][browseable-api] topic for more information about the browsable API feature and how to customize it. +See the [browsable api][browsable-api] topic for more information about the browsable API feature and how to customize it. ## What's next? @@ -170,6 +170,6 @@ In [tutorial part 3][tut-3], we'll start using class based views, and see how ge [json-url]: http://example.com/api/items/4.json [devserver]: http://127.0.0.1:8000/snippets/ -[browseable-api]: ../topics/browsable-api.md +[browsable-api]: ../topics/browsable-api.md [tut-1]: 1-serialization.md [tut-3]: 3-class-based-views.md diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md index 878672bb..f1ec862e 100644 --- a/docs/tutorial/4-authentication-and-permissions.md +++ b/docs/tutorial/4-authentication-and-permissions.md @@ -118,17 +118,17 @@ Then, add the following property to **both** the `SnippetList` and `SnippetDetai permission_classes = (permissions.IsAuthenticatedOrReadOnly,) -## Adding login to the Browseable API +## Adding login to the Browsable API -If you open a browser and navigate to the browseable API at the moment, you'll find that you're no longer able to create new code snippets. In order to do so we'd need to be able to login as a user. +If you open a browser and navigate to the browsable API at the moment, you'll find that you're no longer able to create new code snippets. In order to do so we'd need to be able to login as a user. -We can add a login view for use with the browseable API, by editing our URLconf once more. +We can add a login view for use with the browsable API, by editing our URLconf once more. Add the following import at the top of the file: from django.conf.urls import include -And, at the end of the file, add a pattern to include the login and logout views for the browseable API. +And, at the end of the file, add a pattern to include the login and logout views for the browsable API. urlpatterns += patterns('', url(r'^api-auth/', include('rest_framework.urls', diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md index 27a10840..9ac64eed 100644 --- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md +++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md @@ -149,11 +149,11 @@ We could also customize the pagination style if we needed too, but in this case ## Reviewing our work -If we open a browser and navigate to the browseable API, you'll find that you can now work your way around the API simply by following links. +If we open a browser and navigate to the browsable API, you'll find that you can now work your way around the API simply by following links. You'll also be able to see the 'highlight' links on the snippet instances, that will take you to the highlighted code HTML representations. -We've now got a complete pastebin Web API, which is fully web browseable, and comes complete with authentication, per-object permissions, and multiple renderer formats. +We've now got a complete pastebin Web API, which is fully web browsable, and comes complete with authentication, per-object permissions, and multiple renderer formats. We've walked through each step of the design process, and seen how if we need to customize anything we can gradually work our way down to simply using regular Django views. diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index 74084541..d84ee46b 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -121,7 +121,7 @@ Firstly the names `user-detail` and `group-detail` are important. We're using t Secondly, we're modifying the urlpatterns using `format_suffix_patterns`, to append optional `.json` style suffixes to our URLs. -Finally, we're including default login and logout views for use with the browsable API. That's optional, but useful if your API requires authentication and you want to use the browseable API. +Finally, we're including default login and logout views for use with the browsable API. That's optional, but useful if your API requires authentication and you want to use the browsable API. ## Settings -- cgit v1.2.3 From 74beaefd1205503c06fdff8bb2621ba4c8c5baaa Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 2 May 2013 12:08:05 +0100 Subject: Simplifying bits of docs --- docs/tutorial/6-viewsets-and-routers.md | 8 +-- docs/tutorial/quickstart.md | 95 +++++++++------------------------ 2 files changed, 29 insertions(+), 74 deletions(-) (limited to 'docs/tutorial') diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index 25a974a1..4b01d3e0 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -105,8 +105,8 @@ Here's our re-wired `urls.py` file. # Create a router and register our viewsets with it. router = DefaultRouter() - router.register(r'snippets', views.SnippetViewSet, name='snippet') - router.register(r'users', views.UserViewSet, name='user') + router.register(r'snippets', views.SnippetViewSet) + router.register(r'users', views.UserViewSet) # The API URLs are now determined automatically by the router. # Additionally, we include the login URLs for the browseable API. @@ -115,7 +115,7 @@ Here's our re-wired `urls.py` file. url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) ) -Registering the viewsets with the router is similar to providing a urlpattern. We include three arguments - the URL prefix for the views, the viewset itself, and the base name that should be used for constructing the URL names, such as `snippet-list`. +Registering the viewsets with the router is similar to providing a urlpattern. We include two arguments - the URL prefix for the views, and the viewset itself. The `DefaultRouter` class we're using also automatically creates the API root view for us, so we can now delete the `api_root` method from our `views` module. @@ -123,7 +123,7 @@ The `DefaultRouter` class we're using also automatically creates the API root vi Using viewsets can be a really useful abstraction. It helps ensure that URL conventions will be consistent across your API, minimizes 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. +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 viewsets is less explicit than building your views individually. ## Reviewing our work diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index 74084541..a7716105 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -8,7 +8,7 @@ Create a new Django project, and start a new app called `quickstart`. Once you' First up we're going to define some serializers in `quickstart/serializers.py` that we'll use for our data representations. - from django.contrib.auth.models import User, Group, Permission + from django.contrib.auth.models import User, Group from rest_framework import serializers @@ -19,109 +19,64 @@ First up we're going to define some serializers in `quickstart/serializers.py` t class GroupSerializer(serializers.HyperlinkedModelSerializer): - permissions = serializers.ManySlugRelatedField( - slug_field='codename', - queryset=Permission.objects.all() - ) - class Meta: model = Group - fields = ('url', 'name', 'permissions') + fields = ('url', 'name') Notice that we're using hyperlinked relations in this case, with `HyperlinkedModelSerializer`. You can also use primary key and various other relationships, but hyperlinking is good RESTful design. -We've also overridden the `permission` field on the `GroupSerializer`. In this case we don't want to use a hyperlinked representation, but instead use the list of permission codenames associated with the group, so we've used a `ManySlugRelatedField`, using the `codename` field for the representation. - ## Views Right, we'd better write some views then. Open `quickstart/views.py` and get typing. from django.contrib.auth.models import User, Group - from rest_framework import generics - from rest_framework.decorators import api_view - from rest_framework.reverse import reverse - from rest_framework.response import Response + from rest_framework import viewsets from quickstart.serializers import UserSerializer, GroupSerializer - @api_view(['GET']) - def api_root(request, format=None): - """ - The entry endpoint of our API. - """ - return Response({ - 'users': reverse('user-list', request=request), - 'groups': reverse('group-list', request=request), - }) - - - class UserList(generics.ListCreateAPIView): - """ - API endpoint that represents a list of users. - """ - model = User - serializer_class = UserSerializer - - - class UserDetail(generics.RetrieveUpdateDestroyAPIView): + class UserViewSet(viewsets.ModelViewSet): """ - API endpoint that represents a single user. + API endpoint that allows users to be viewed or edited. """ - model = User + queryset = User.objects.all() serializer_class = UserSerializer - class GroupList(generics.ListCreateAPIView): + class GroupViewSet(viewsets.ModelViewSet): """ - API endpoint that represents a list of groups. + API endpoint that allows groups to be viewed or edited. """ - model = Group - serializer_class = GroupSerializer - - - class GroupDetail(generics.RetrieveUpdateDestroyAPIView): - """ - API endpoint that represents a single group. - """ - model = Group + queryset = Group.objects.all() serializer_class = GroupSerializer -Let's take a moment to look at what we've done here before we move on. We have one function-based view representing the root of the API, and four class-based views which map to our database models, and specify which serializers should be used for representing that data. Pretty simple stuff. +Rather that write multiple views we're grouping together all the common behavior into classes called `ViewSets`. + +We can easily break these down into individual views if we need to, but using viewsets keeps the view logic nicely organized as well as being very concise. ## URLs -Okay, let's wire this baby up. On to `quickstart/urls.py`... +Okay, now let's wire up the API URLs. On to `quickstart/urls.py`... from django.conf.urls import patterns, url, include - from rest_framework.urlpatterns import format_suffix_patterns - from quickstart.views import UserList, UserDetail, GroupList, GroupDetail - - - urlpatterns = patterns('quickstart.views', - url(r'^$', 'api_root'), - url(r'^users/$', UserList.as_view(), name='user-list'), - url(r'^users/(?P\d+)/$', UserDetail.as_view(), name='user-detail'), - url(r'^groups/$', GroupList.as_view(), name='group-list'), - url(r'^groups/(?P\d+)/$', GroupDetail.as_view(), name='group-detail'), - ) - - - # Format suffixes - urlpatterns = format_suffix_patterns(urlpatterns, allowed=['json', 'api']) + from rest_framework import routers + from quickstart import views + router = routers.DefaultRouter() + router.register(r'users', views.UserViewSet) + router.register(r'groups', views.GroupViewSet) - # Default login/logout views - urlpatterns += patterns('', + # Wire up our API using automatic URL routing. + # Additionally, we include login URLs for the browseable API. + urlpatterns = patterns('', + url(r'^', include(router.urls)), url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) ) -There's a few things worth noting here. - -Firstly the names `user-detail` and `group-detail` are important. We're using the default hyperlinked relationships without explicitly specifying the view names, so we need to use names of the style `{modelname}-detail` to represent the model instance views. +Because we're using viewsets instead of views, we can automatically generate the URL conf for our API, by simply registering the viewsets with a router class. -Secondly, we're modifying the urlpatterns using `format_suffix_patterns`, to append optional `.json` style suffixes to our URLs. +Again, if we need more control over the API URLs we can simply drop down to using regular class based views, and writing the URL conf explicitly. -Finally, we're including default login and logout views for use with the browsable API. That's optional, but useful if your API requires authentication and you want to use the browseable API. +Note that we're also including default login and logout views for use with the browsable API. That's optional, but useful if your API requires authentication and you want to use the browseable API. ## Settings -- cgit v1.2.3 From a8e090df04c9574aee9990f82715841aa797e220 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 7 May 2013 17:14:25 +0100 Subject: Remove accidental conflict diff --- docs/tutorial/quickstart.md | 4 ---- 1 file changed, 4 deletions(-) (limited to 'docs/tutorial') diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index 627724c7..52fe3acf 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -76,11 +76,7 @@ Because we're using viewsets instead of views, we can automatically generate the Again, if we need more control over the API URLs we can simply drop down to using regular class based views, and writing the URL conf explicitly. -<<<<<<< HEAD -Note that we're also including default login and logout views for use with the browsable API. That's optional, but useful if your API requires authentication and you want to use the browseable API. -======= Finally, we're including default login and logout views for use with the browsable API. That's optional, but useful if your API requires authentication and you want to use the browsable API. ->>>>>>> master ## Settings -- cgit v1.2.3 From 70b8e5b759342cb746337c768f9af475ca5dc756 Mon Sep 17 00:00:00 2001 From: Daniel Hepper Date: Tue, 7 May 2013 19:16:03 +0200 Subject: Fixed typos in tutorial --- docs/tutorial/1-serialization.md | 2 +- docs/tutorial/4-authentication-and-permissions.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'docs/tutorial') diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index 205ee7e0..ed54a876 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -204,7 +204,7 @@ We can also serialize querysets instead of model instances. To do so we simply ## Using ModelSerializers -Our `SnippetSerializer` class is replicating a lot of information that's also contained in the `Snippet` model. It would be nice if we could keep out code a bit more concise. +Our `SnippetSerializer` class is replicating a lot of information that's also contained in the `Snippet` model. It would be nice if we could keep our code a bit more concise. In the same way that Django provides both `Form` classes and `ModelForm` classes, REST framework includes both `Serializer` classes, and `ModelSerializer` classes. diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md index 8bb3164b..f6c3efb0 100644 --- a/docs/tutorial/4-authentication-and-permissions.md +++ b/docs/tutorial/4-authentication-and-permissions.md @@ -143,7 +143,7 @@ Once you've created a few code snippets, navigate to the '/users/' endpoint, and ## Object level permissions -Really we'd like all code snippets to be visible to anyone, but also make sure that only the user that created a code snippet is able update or delete it. +Really we'd like all code snippets to be visible to anyone, but also make sure that only the user that created a code snippet is able to update or delete it. To do that we're going to need to create a custom permission. -- cgit v1.2.3 From fd84cf7f10bf703c5daae4a5f6a7dee0c22471dd Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 10 May 2013 21:56:33 +0100 Subject: Docs tweaks --- docs/tutorial/2-requests-and-responses.md | 2 +- docs/tutorial/6-viewsets-and-routers.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'docs/tutorial') diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md index 3a002cb0..260c4d83 100644 --- a/docs/tutorial/2-requests-and-responses.md +++ b/docs/tutorial/2-requests-and-responses.md @@ -8,7 +8,7 @@ Let's introduce a couple of essential building blocks. REST framework introduces a `Request` object that extends the regular `HttpRequest`, and provides more flexible request parsing. The core functionality of the `Request` object is the `request.DATA` attribute, which is similar to `request.POST`, but more useful for working with Web APIs. request.POST # Only handles form data. Only works for 'POST' method. - request.DATA # Handles arbitrary data. Works any HTTP request with content. + request.DATA # Handles arbitrary data. Works for 'POST', 'PUT' and 'PATCH' methods. ## Response objects diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index 4b01d3e0..277804e2 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -119,7 +119,7 @@ Registering the viewsets with the router is similar to providing a urlpattern. The `DefaultRouter` class we're using also automatically creates the API root view for us, so we can now delete the `api_root` method from our `views` module. -## Trade-offs between views vs viewsets. +## Trade-offs between views vs viewsets Using viewsets can be a really useful abstraction. It helps ensure that URL conventions will be consistent across your API, minimizes 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. -- cgit v1.2.3