Generic ViewSets — Custom Endpoints
Welcome back! For those of you who are new here, this article is a part of a series that you may check out here. However, you don’t need a lot of context if you’re using this as a reference.
Let’s get started… here are a list of things you should be familiar with:
You can go through this article if you want a quick refresher.
First, our aim:
Last time, we created our User model, User Serializer and our UserViewSet. The project is on Github here. Again, if you’re not following the series, don’t panic, you don’t need to know the setup to understand the topic.
Today, our aim is to create a custom endpoint for retrieving the current users information.
Let’s start with the Custom Endpoint, here is how we’re coding our endpoint right now:
Even though this is much cleaner than using a ModelViewSet, it’s still not optimum.
- For one, we are defining a
queryset
that is meant to return exactly one user. That’s the opposite of what aquery-SET
is meant to do. - Another not-so-apparent problem is that we’re using the inbuilt
List
endpoint to return a single user’s information, not a list.
The solution to tackle these problems is to use a custom endpoint. Let’s call this endpoint the me
endpoint.It always returns information about the current user.
Custom Endpoints:
- We first define a function called
me
inside theUserViewSet
class. By naming the function me, our endpoint’sURL
will also becomewww.hostname.com/users/me
. We will pass two arguments tome
:self
andrequest
. If you’ve gotten this far, I’m sure you’re familiar with these arguments.
def me(self, request):
pass
2. We will then get the serializer
from self
.
def me(self, request):
serializer = self.get_serializer_class()
3. Then we get the serialized data
for the current user and return that as a response.
def me(self, request):
serializer = self.get_serializer_class()
data = serializer(request.user).data
return Response(data, status=status.HTTP_200_OK)
Pro Tip: Avoid using numbers when setting the status codes. Always use the inbuilt status codes that Django Rest Framework provides. This makes your code scalable in case (very unlikely) the status codes are changed. i.e.-
return Response(data, status=200)
— — — Wrong
return Response(data, status=status.HTTP_200_OK)
— — — CorrectHere is a list of DRF’s status codes for your reference.
4. The last step is to use the action decorator
. If you are not familiar with what a decorator
is:
decorators
are a tool in Python that allow programmers to modify the behavior of an existing function or class. Decorators allow us to wrap another function in order to extend the behavior of the wrapped function, without permanently modifying it.Essentially, a decorator is a piece of code (A) that changes the behavior of another piece of code (B) without actually changing the code (B) itself. When this happens, we say that the we are wrapping a piece of code (B) with a decorator (A).
This is an excellent resource to learn more about decorators.
The action decorator
wraps the function we just made. It converts the normal python function into an endpoint. Here is the syntax:
@action(methods=['get', 'post', 'patch'], detail=True, permission_classes=[IsAuthenticated. IsAdmin])
Let’s break it down,
methods
: These are the HTTP request types that the endpoint accepts.detail
: This option decides whether the endpoint is a detail view or a general view. A detail view usually contains the primary key of an object in the URL. For example:api.domain.com/users/3/
is a detail view of the user withuser_id=3
.permission_classes
: This is similar to the parent class’permission_classes
option. This allows you to set custom permissions per endpoint if you want. You can omit this option to inherit the parent class’ permissions.
Pro Tip: If you decide to set custom permission classes per endpoint, remember that the parent class’ permissions don’t get added to the permissions you add. Essentially:
Finally, let’s see how the code looks after adding this new endpoint:
Here’s the breakdown:
- We got rid of the
ListModelMixin
, we don’t need that anymore. - We changed the
get_queryset
to return all the users now, because we aren’t even using thequeryset
in theme
endpoint anymore. In the future however, there may be endpoints in thisViewSet
that do need thequeryset
, so we will set it up for the future. - We added a new function who’s structure we already discussed. But we also added our decorator:
- We are only allowing theget
method for now.
- It’s not a detail endpoint, it will always only return the current user’s info.
- We are not overriding the parent class’ permissions.
And here we are. We have a functional, clean, and optimum view to retrieve the current user’s profile information. Let’s have a look at it’s response before we wrap things up.
To get a response, we need to connect a URL
to our Generic ViewSet
. Because our focus is the ViewSet, we will use a shorter way to create the URL
. We will explore the optimum way of managing URLs
in a later series. (Just in case you’re curious, we should use routers
, you can find more about them here).
Add the URL
in the urls.py
of the main app.
On calling the http://localhost:8000/users/me
endpoint, this is the response:
Fantastic! Hopefully, this is enough information for you to start making custom endpoints for any purpose you need. We have covered a lot in this article. Go through it as many times as you need, if you face any issues, you can leave a comment here and I’ll do my best to help you out.
Well, we can wrap it up here. We haven’t dealt with multiple serializers
, multiple querysets
, custom mixins
and such yet, but we will very soon.
Stay tuned and I’ll see you soon.