Views are my favorite part of Django Rest Framework. To me this is where the coolest logic happens, choosing which data to show and who to show it to. The general class structure goes APIView > GenericAPIView > ViewSet with of course many options and mixins along the way that make it work. Let's explore!
We're going to get right to it. ViewSets provide you the most functionality for the smallest amount of code. You can get a full set of endpoints to read, create, update and delete with minimal code for a given model.
# determine which data to show, and how to serialize the data class KittenViewSet(ModelViewSet): queryset = Kitten.objects.all() serializer_class = KittenSerializer
You can also change up the viewsets by inheriting from
viewsets.GenericViewSet. Let's take a look what's behind the scenes where the magic of ViewSets happens.
Note: if you haven't already, please start with DRF Basics
Let's say that on our Kitten model we want to just be able to perform read-only function (retrieve and list). Well, luckily for us Django Rest Framework has a built-in class for that called
viewsets.ReadOnlyModelViewSet. By using that class instead of the ModelViewSet class, we will only be able to perform retrieve and list actions - delete, update, create will all return 405 NOT ALLOWED responses.
from rest_framework.viewsets import ReadOnlyModelViewSet # Only allow read-only safe methods - no create/update/delete class KittenViewSet(ReadOnlyModelViewSet): queryset = Kitten.objects.all() serializer_class = KittenSerializer
However, let's say that we want to also include the ability to create kittens. After all, who wouldn't want to create kittens?
To do that, we can inherit from GenericViewSet model and add the mixins that we want. We'll be adding 3 mixins to Retrieve, List and Create.
from rest_framework import mixins from rest_framework.viewsets import GenericViewSet class RetrieveListCreateViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.ListModelMixin, GenericViewSet): """ A viewset that provides `create()`, `retrieve()`, and `list()` actions. """ pass class KittenViewSet(RetrieveListCreateViewSet): queryset = Kitten.objects.all() serializer_class = KittenSerializer
Using Generic Views is similar to ViewSets in the sense that we're working with data in django models, but we have a bit more control because we're able to hook into the method hooks (get, post, put, delete, patch). ViewSets are nice when everything is perfect and dandy, but in the real world, things aren't always like that.
The magic of viewsets are that it directly relates a get request to a retrieve or list action. It relates post to a create action, put to an update action, and delete to a destroy action. What happens when we want to change that up?
With generic views we build our view with GenericAPIView and whichever mixins we want to be added. The mixins are what provide CRUD functionality. This is also where many functions and properties are defined (serializer_class, queryset, filter_backends, and more!).
So let's look at our example above where we want to get a list of kittens, get a specific kitten, and create new kittens.
from rest_framework import generics, mixins class PuppyView(generics.ListCreateAPIView): queryset = Puppy.objects.all() serializer_class = PuppySerializer class PuppyDetailView(generics.RetrieveAPIView): queryset = Puppy.objects.all() serializer_class = PuppySerializer
As you can see, the code above looks pretty similar to when we use ViewSets. There's one catch though, we're not including the ViewSetMixin which gets applied to ViewSet classes. You might ask, "so what the hell does that mean"? And the answer is that it won't automatically add urls into our project via the DRF router, and it doesn't automatically associate a request verb with a model action. We'll need to add the URL pattern as well as associate a request with an action (this part is usually done through a generic class - RetrieveAPIView, ListCreateAPIView above)
urlpatterns = patterns('', ... url(r'puppies/$', PuppyView.as_view(), name="puppy-list"), url(r'puppies/(?P
\d+)/$', PuppyDetailView.as_view(), name="puppy-detail"), )
Ok, so now we're feeling pretty good with ViewSets and GenericViews. However, there is something important to notice - everything is derived from a class called
APIView, what is that all about?
What happens when we want to work with data or actions that don't revolve around django models?
As of now, we have a way to perform actions on django models. But what happens when we need to work with external API data? That would be pretty cool, right? That's where APIView comes into play. Let's dive in!
Let's take an example where we want to make a get request to our endpoint to fetch External API data. Usually to fetch external API data we need to provide a key and secret, and that should always be done on the server.
class ExternalAPIView(APIView): def get(self, request): data = send_request_external_api() return Response(data)
We'll do something like this, where send_request_eternal_api can be a whole slew of different things depending on the API.
Hopefully in this section you learned that we can handle HTTP Requests in different ways than generic CRUD operations on a django model. The server can do anything it wants when receiving a request, it's just that ViewSets and GenericAPIView handle to the most common actions dealing with our internal data.
Let's hop back up to ViewSets and explore some of the class properties we can set. As we know, ViewSets are at the top of the foodchain - it inherits GenericAPIView and APIView so these properties are actually defined lower in GenericAPIView and APIView. Because of the beauty of inheritance, ViewSet has access to all of them.
queryset allows us to control what data is returned, so for example we can limit our puppies to be less than 2 years old.
permission_classesallows us to control who can view the data. This is a bit complex and there will be a post specifically for this topic, but you'll see a basic example below.
serializer_class This is a required property and it lets the view know how to turn the data from data into a form that can be viewable in the browser.
There are plenty more properties that you'll come across, but these are the most common from my experience.
class KittenViewSet(RetrieveListCreateViewSet): queryset = Kitten.objects.filter(age__lt=2) serializer_class = KittenSerializer permission_classes = (IsAdminUser, )
I just want to reiterate the inheritance chain and when to use which layer, it is something I wish I understood long ago.
APIView is the base class for all API views. This is the class we use when not dealing with internal django models. In most cases it is used with external APIs.
GenericAPIView is used when you want to have custom URL endpoints or hook into the request method (get, post, put, delete, patch) before getting into the action (retrieve, list, create, update, destroy). Most of the time you can use ViewSet, but there will be some situations where you need more flexibility.
ViewSet provides you everything you need for default actions with Django data. It is built off of GenericAPIView and performs default actions based off of the type of method received. To add into your urls, you just need to add it into a router.