Generic ViewSets — Custom Endpoints

Caronex Labs
Django Rest for Not Beginners
5 min readJul 12, 2020

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:

users_module/views.py

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 a query-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:

  1. We first define a function called me inside the UserViewSet class. By naming the function me, our endpoint’s URL will also become www.hostname.com/users/me. We will pass two arguments to me: self and request. 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) — — — Correct

Here 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 with user_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:

users_module/views.py

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 the queryset in the me endpoint anymore. In the future however, there may be endpoints in this ViewSet that do need the queryset, 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 the get 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.

_1_GenericViewSets/urls.py

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.

--

--