The default behavior of REST framework's generic list views is to return the entire queryset for a model manager. Often you will want your API to restrict the items that are returned by the queryset.
The default behavior of REST framework's generic list views is to return the entire queryset for a model manager. Often you will want your API to restrict the items that are returned by the queryset.
The simplest way to filter the queryset of any view that subclasses `MultipleObjectAPIView` is to override the `.get_queryset()` method.
The simplest way to filter the queryset of any view that subclasses `GenericAPIView` is to override the `.get_queryset()` method.
Overriding this method allows you to customize the queryset returned by the view in a number of different ways.
Overriding this method allows you to customize the queryset returned by the view in a number of different ways.
...
@@ -21,7 +21,6 @@ You can do so by filtering based on the value of `request.user`.
...
@@ -21,7 +21,6 @@ You can do so by filtering based on the value of `request.user`.
For example:
For example:
class PurchaseList(generics.ListAPIView)
class PurchaseList(generics.ListAPIView)
model = Purchase
serializer_class = PurchaseSerializer
serializer_class = PurchaseSerializer
def get_queryset(self):
def get_queryset(self):
...
@@ -44,7 +43,6 @@ For example if your URL config contained an entry like this:
...
@@ -44,7 +43,6 @@ For example if your URL config contained an entry like this:
You could then write a view that returned a purchase queryset filtered by the username portion of the URL:
You could then write a view that returned a purchase queryset filtered by the username portion of the URL:
class PurchaseList(generics.ListAPIView)
class PurchaseList(generics.ListAPIView)
model = Purchase
serializer_class = PurchaseSerializer
serializer_class = PurchaseSerializer
def get_queryset(self):
def get_queryset(self):
...
@@ -62,7 +60,6 @@ A final example of filtering the initial queryset would be to determine the init
...
@@ -62,7 +60,6 @@ A final example of filtering the initial queryset would be to determine the init
We can override `.get_queryset()` to deal with URLs such as `http://example.com/api/purchases?username=denvercoder9`, and filter the queryset only if the `username` parameter is included in the URL:
We can override `.get_queryset()` to deal with URLs such as `http://example.com/api/purchases?username=denvercoder9`, and filter the queryset only if the `username` parameter is included in the URL:
class PurchaseList(generics.ListAPIView)
class PurchaseList(generics.ListAPIView)
model = Purchase
serializer_class = PurchaseSerializer
serializer_class = PurchaseSerializer
def get_queryset(self):
def get_queryset(self):
...
@@ -100,7 +97,7 @@ You must also set the filter backend to `DjangoFilterBackend` in your settings:
...
@@ -100,7 +97,7 @@ You must also set the filter backend to `DjangoFilterBackend` in your settings:
If all you need is simple equality-based filtering, you can set a `filter_fields` attribute on the view, listing the set of fields you wish to filter against.
If all you need is simple equality-based filtering, you can set a `filter_fields` attribute on the view, listing the set of fields you wish to filter against.
class ProductList(generics.ListAPIView):
class ProductList(generics.ListAPIView):
model = Product
queryset = Product.objects.all()
serializer_class = ProductSerializer
serializer_class = ProductSerializer
filter_fields = ('category', 'in_stock')
filter_fields = ('category', 'in_stock')
...
@@ -120,7 +117,7 @@ For more advanced filtering requirements you can specify a `FilterSet` class tha
...
@@ -120,7 +117,7 @@ For more advanced filtering requirements you can specify a `FilterSet` class tha
Some Web frameworks such as Rails provide functionality for automatically determining how the URLs for an application should be mapped to the logic that deals with handling incoming requests.
Some Web frameworks such as Rails provide functionality for automatically determining how the URLs for an application should be mapped to the logic that deals with handling incoming requests.
Conversely, Django stops short of automatically generating URLs, and requires you to explicitly manage your URL configuration.
REST framework adds support for automatic URL routing to Django, and provides you with a simple, quick and consistent way of wiring your view logic to a set of URLs.
REST framework adds support for automatic URL routing, which provides you with a simple, quick and consistent way of wiring your view logic to a set of URLs.
## Usage
# API Guide
Here's an example of a simple URL conf, that uses `DefaultRouter`.
Routers provide a convenient and simple shortcut for wiring up your application's URLs.
@@ -111,26 +111,25 @@ Again, as with `ModelViewSet`, you can use any of the standard attributes and me
...
@@ -111,26 +111,25 @@ Again, as with `ModelViewSet`, you can use any of the standard attributes and me
Any standard `View` class can be turned into a `ViewSet` class by mixing in `ViewSetMixin`. You can use this to define your own base classes.
Any standard `View` class can be turned into a `ViewSet` class by mixing in `ViewSetMixin`. You can use this to define your own base classes.
For example, the definition of `ModelViewSet` looks like this:
## Example
class ModelViewSet(mixins.CreateModelMixin,
For example, we can create a base viewset class that provides `retrieve`, `update` and `list` operations:
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
class RetrieveUpdateListViewSet(mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
mixins.UpdateModelMixin,
mixins.ListModelMixin,
mixins.ListModelMixin,
viewsets.ViewSetMixin,
viewsets.ViewSetMixin,
generics.GenericAPIView):
generics.GenericAPIView):
"""
"""
A viewset that provides actions for `create`, `retrieve`,
A viewset that provides `retrieve`, `update`, and `list` actions.
`update`, `destroy` and `list` actions.
To use it, override the class and set the `.queryset`
To use it, override the class and set the `.queryset` and
and `.serializer_class` attributes.
`.serializer_class` attributes.
"""
"""
pass
pass
By creating your own base `ViewSet` classes, you can provide common behavior that can be reused in multiple views across your API.
By creating your own base `ViewSet` classes, you can provide common behavior that can be reused in multiple views across your API.
For advanced usage, it's worth noting the that `ViewSetMixin` class can also be applied to the standard Django `View` class. Doing so allows you to use REST framework's automatic routing, but don't want to use it's permissions, authentication and other API policies.
For advanced usage, it's worth noting the that `ViewSetMixin` class can also be applied to the standard Django `View` class. Doing so allows you to use REST framework's automatic routing with regular Django views.
REST framework 2.3 is geared towards making it easier and quicker to build your Web APIs.
## ViewSets & Routers
We've introduced
## Easier Serializers
REST framework lets you be totally explict regarding how you want to represent relationships, allowing you to choose between styles such as hyperlinking or primary key relationships.
The ability to specify exactly how you want to represent relationships is powerful, but it also introduces complexity. In order to keep things more simple, REST framework now allows you to include reverse relationships simply by including the field name in the `fields` metadata of the serializer class.
For example, in REST framework 2.2, reverse relationships needed to be included explicitly on a serializer class.
class BlogSerializer(serializers.ModelSerializer):
This release rationalises the API and implementation of the Generic views, dropping the dependancy on Django's `SingleObjectMixin` and `MultipleObjectMixin` classes, removing a number of unneeded attributes, and generally making the implementation more obvious and easy to work with.
This improvement is reflected in improved documentation for the `GenericAPIView` base class, and should make it easier to determine how to override methods on the base class if you need to write customized subclasses.
---
## API Changes
### Simplified generic view classes
The functionality provided by `SingleObjectAPIView` and `MultipleObjectAPIView` base classes has now been moved into the base class `GenericAPIView`. The implementation of this base class is simple enough that providing subclasses for the base classes of detail and list views is somewhat unnecessary.
Additionally the base generic view no longer inherits from Django's `SingleObjectMixin` or `MultipleObjectMixin` classes, simplifying the implementation, and meaning you don't need to cross-reference across to Django's codebase.
Using the `SingleObjectAPIView` and `MultipleObjectAPIView` base classes continues to be supported, but will raise a `PendingDeprecationWarning`. You should instead simply use `GenericAPIView` as the base for any generic view subclasses.
### Removed attributes
The following attributes and methods, were previously present as part of Django's generic view implementations, but were unneeded and unusedand have now been entirely removed.
* context_object_name
* get_context_data()
* get_context_object_name()
The following attributes and methods, which were previously present as part of Django's generic view implementations have also been entirely removed.
* paginator_class
* get_paginator()
* get_allow_empty()
* get_slug_field()
There may be cases when removing these bits of API might mean you need to write a little more code if your view has highly customized behavior, but generally we believe that providing a coarser-grained API will make the views easier to work with, and is the right trade-off to make for the vast majority of cases.
Note that the listed attributes and methods have never been a documented part of the REST framework API, and as such are not covered by the deprecation policy.
### Simplified methods
The `get_object` and `get_paginate_by` methods no longer take an optional queryset argument. This makes overridden these methods more obvious, and a little more simple.
Using an optional queryset with these methods continues to be supported, but will raise a `PendingDeprecationWarning`.
### Deprecated attributes
The following attributes are used to control queryset lookup, and have all been moved into a pending deprecation state.
* pk_url_kwarg = 'pk'
* slug_url_kwarg = 'slug'
* slug_field = 'slug'
Their usage is replaced with a single attribute:
* lookup_field = 'pk'
This attribute is used both as the regex keyword argument in the URL conf, and as the model field to filter against when looking up a model instance. To use non-pk based lookup, simply set the `lookup_field` argument to an alternative field, and ensure that the keyword argument in the url conf matches the field name.
For example, a view with 'username' based lookup might look like this:
class UserDetail(generics.RetrieveAPIView):
lookup_field = 'username'
queryset = User.objects.all()
serializer_class = UserSerializer
And would have the following entry in the urlconf:
Usage of the old-style attributes continues to be supported, but will raise a `PendingDeprecationWarning`.
## Other notes
### Explict view attributes
The usage of `model` attribute in generic Views is still supported, but it's usage is being discouraged in favour of using explict `queryset` and `serializer_class` attributes.
For example, the following is now the recommended style for using generic views:
class AccountListView(generics.RetrieveAPIView):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
Using explict `queryset` and `serializer_class` attributes makes the functioning of the view more clear than using the shortcut `model` attribute.
It also makes it the usage of overridden `get_queryset()` or `get_serializer_class()` methods more obvious.
class AccountListView(generics.RetrieveAPIView):
serializer_class = MyModelSerializer
def get_queryset(self):
"""
Determine the queryset dynamically, depending on the
user making the request.
Note that overriding this method follows on more obviously now
that an explicit `queryset` attribute is the usual view style.
"""
return self.user.accounts
### Django 1.3 support
The 2.3 release series will be the last series to provide compatiblity with Django 1.3.
@@ -92,8 +92,8 @@ Let's take a look at how we can compose our views by using the mixin classes.
...
@@ -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,
class SnippetList(mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.CreateModelMixin,
generics.MultipleObjectAPIView):
generics.GenericAPIView):
model = Snippet
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
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.
...
@@ -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):
def post(self, request, *args, **kwargs):
return self.create(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.
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,
class SnippetDetail(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.DestroyModelMixin,
generics.SingleObjectAPIView):
generics.GenericAPIView):
model = Snippet
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
def get(self, request, *args, **kwargs):
...
@@ -122,7 +122,7 @@ The base class provides the core functionality, and the mixin classes provide th
...
@@ -122,7 +122,7 @@ The base class provides the core functionality, and the mixin classes provide th
def delete(self, request, *args, **kwargs):
def delete(self, request, *args, **kwargs):
return self.destroy(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
## Using generic class based views
...
@@ -134,12 +134,12 @@ Using the mixin classes we've rewritten the views to use slightly less code than
...
@@ -134,12 +134,12 @@ Using the mixin classes we've rewritten the views to use slightly less code than
class SnippetList(generics.ListCreateAPIView):
class SnippetList(generics.ListCreateAPIView):
model = Snippet
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
serializer_class = SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
model = Snippet
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
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.
Wow, that's pretty concise. We've gotten a huge amount for free, and our code looks like good, clean, idiomatic Django.
@@ -68,12 +68,12 @@ Because `'snippets'` is a *reverse* relationship on the User model, it will not
...
@@ -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.
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):
class UserList(generics.ListAPIView):
model = User
queryset = User.objects.all()
serializer_class = UserSerializer
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
class UserDetail(generics.RetrieveAPIView):
model = User
queryset = User.objects.all()
serializer_class = UserSerializer
serializer_class = UserSerializer
Finally we need to add those views into the API, by referencing them from the URL conf.
Finally we need to add those views into the API, by referencing them from the URL conf.
@@ -143,34 +143,16 @@ We can change the default list style to use pagination, by modifying our `settin
...
@@ -143,34 +143,16 @@ We can change the default list style to use pagination, by modifying our `settin
'PAGINATE_BY': 10
'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.
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.
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.
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.
[tut-6]:6-viewsets-and-routers.md
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.
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`.
`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
...
@@ -19,7 +19,7 @@ First of all let's refactor our `UserListView` and `UserDetailView` views into a
queryset = User.objects.all()
queryset = User.objects.all()
serializer_class = UserSerializer
serializer_class = UserSerializer
Here we've used `ReadOnlyModelViewSet` class to automatically provide the default 'read-only' operations. We're still setting the `queryset` and `serializer_class` attributes exactly as we did when we were using regular views, but we no longer need to provide the same information to two seperate classes.
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.
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.
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.
## 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.
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.