Writing Custom Routes in Django RestFrameWork (DRF) Viewsets.
Tags: django, python, djangorestframework, backend
Published: 2021-12-01 09:31:38

Django RestFrameWork (DRF) has proven to be the most valuable and helpful library for Django. It's viewset capability is an admirable resource to enable backend devs define robust urls with embedded route structures. These routes would have required you to write a unique url; but thanks to the resourceful context of viewsets from DRF. I'd be sharing some helpful insight to harness your use of the DRF. This would be categorised on the following:
<b>i)</b> Decorators
<b>ii)</b> custom routes
Let's get on then. 
Now, let's take an authentication url for instance, the functionalities you'd definitely want to incorporate, would include, but definitely not exclusive to the following;
a) signup
b) signin
c) forgot password
Personally, I'd include more routes (according to Django's legacy or rules: explicit is better than implicit);
d) OTP 
e) user-details 
You could have some other you'd like to include, provided they fall under the collective action of authentication and authorizaion.
Now, here comes the question: wouldn't it be cumbersome, that you'd have to write a unique view and then, define a unique url for all these authentication actions? definitely, it would (I don't need to be told).
That's what DRFs' viewsets helps us solve; and as the name implies, "view-sets". You gerrit? Sure you did.
Let's define an API then, using the DRF's viewsets. I'll section the procedures as below;
<b>i) Start a new project</b> I'll assume you're acquainted with django's best practices about starting a new project i.e creating a virtual environment, defining env files etc. Secondly, the folder structure, for a newly created app in django comes with a views.py file. You can rename that to viewsets.py.
<b>ii) Define the viewset class for users</b> (say <b>users</b> is an app on your django project): 
```
from rest_framework import viewsets, status
from .serializers import UsersSerilizer # assuming you've also defined a serializer
from .models import Users # say you've got a users model
from rest_framework.decorators import action
from rest_framework.response import Response

class UsersViewSets(viewsets.ModelViewSet):
    queryset = Users.objects.all() # a queryset variable is mandatory
    serializer_class = UsersSerializer 
    permission_classes = []
    def create(self, request, *args, **kwargs): # ----- for creating a new user
        super().create(request, *args, **kwargs)

    def list(self, request, *args, **kwargs):  # ----- for listing all users
        super().list(request, *args, **kwargs)
    
    def update(self, request, *args, **kwargs):  # ----- for updating a user
        super().update(request, *args, **kwargs)

    @action(methods=["POST"], detail=False, url_path="forgot-    password")
    def forgot_password(self, *args, **kwargs):
        #... logic
        return Response(data=<whatever data>, status=status.HTTP_201_CREATED, content_type="application/json")
```
From the above code, it's obvious that the super keyword is referring to a the methods of a superclass; which implies that you can override those methods as required for your application use case. However, looking at the fourth method, its structure looks different compared to others. Thats a method, defining a custom url: forgot password.
The symbol @, denotes the syntax of a decorator (a function; you can read about decorators in python). This is the procedure for telling DRF that you'd like to define a custom url; because the default methods won't suffice.
The arguments include:
a) methods: what HTTP verb is this url performing?
b) detail: a boolean; tell DRF if this is a detail route i.e if the route construct would require a primary-key, unique email etc, to return a specific instance.
c) url_path: what would you like to call this route?
With the above configuration, you could as well include an otp-verification (just saying) view as well, handle the business logic and return a response (as always). The next step is registering this viewset,so as to be recognised by the app.
<b>iii) Define a router.py file:</b> this is the file from which we'd register all the viewsets in the app i.e you could have product_viewsets, authentication_viewsets, finance_viewsets etc. You'd register a viewset in like so:
```
from rest_framework import routers
app_router = routers.DefaultRouter()
# import the viewsets from your various app module
from users import viewsets as users_viewsets
# register your routers as 
app_router.register("auth or whatever you prefer (avoid trailing slashes)", users_viewsets.UsersViewSet, basename="auth")
#... other viewsets registration
```
This router.py file must be located in the same directory as the applications urls.py file. 
<b>iv) Connect the router.py file to the apps' urls.py file:</b> finally, in the apps' urls.py file, register the router.py file like so:
```
from .router import app_router
from django.urls import path, include

urlpatterns = [
    path("your-site/", include(app_router.urls), 
]
```

we can therefore refer to our custom route as:
```
http://localhost:8000/your-site/auth/forgot-password # for the local server. Don't forget it's https, in production.
```
Let me know if there are places you find discontinuities. Thanks.