aboutsummaryrefslogtreecommitdiffstats
path: root/docs/tutorial
diff options
context:
space:
mode:
Diffstat (limited to 'docs/tutorial')
-rw-r--r--docs/tutorial/3-class-based-views.md16
-rw-r--r--docs/tutorial/4-authentication-and-permissions.md4
-rw-r--r--docs/tutorial/5-relationships-and-hyperlinked-apis.md30
-rw-r--r--docs/tutorial/6-viewsets-and-routers.md151
-rw-r--r--docs/tutorial/quickstart.md97
5 files changed, 195 insertions, 103 deletions
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 f1ec862e..8bb3164b 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 9ac64eed..cb2e092c 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 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 browsable, 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
new file mode 100644
index 00000000..4b01d3e0
--- /dev/null
+++ b/docs/tutorial/6-viewsets-and-routers.md
@@ -0,0 +1,151 @@
+# Tutorial 6 - ViewSets & Routers
+
+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`.
+
+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.ReadOnlyModelViewSet):
+ """
+ This viewset automatically provides `list` and `detail` actions.
+ """
+ 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 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.
+
+ 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 also provide an extra `highlight` action.
+ """
+ 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
+
+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.
+
+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 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',
+ '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.
+
+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<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'),
+ url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
+ url(r'^users/$', user_list, name='user-list'),
+ url(r'^users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail')
+ ))
+
+## Using Routers
+
+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.
+
+ from snippets import views
+ from rest_framework.routers import DefaultRouter
+
+ # Create a router and register our viewsets with it.
+ router = DefaultRouter()
+ 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.
+ 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 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.
+
+## 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.
+
+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
+
+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
diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md
index d84ee46b..627724c7 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,68 @@ 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<pk>\d+)/$', UserDetail.as_view(), name='user-detail'),
- url(r'^groups/$', GroupList.as_view(), name='group-list'),
- url(r'^groups/(?P<pk>\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.
+<<<<<<< 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