Jeremy Satterfield
Coding, Making and Tulsa Life

Abusing Django Rest Framework Part 1: Non-Model Endpoints

When working with Django Rest Framwork a few months back, there were a few road blocks that we ran into. Rest Framwork is awesome with most models for providing a simple CRUD API, in any (or multiple) serializations, with authentication and permissions. Sometimes, however, things aren't so simple. Things get ugly. Framworks get abused.

This is the first of a few posts looking at some of the ways Rest Framwork can be beaten into submission. Some of them may be the way the framework intended them to be implement, but non-obvious, while others were never intended, are probably bad ideas, and should be avoided at all costs (except when the needs of the business require it, of course).

Creating Endpoints to Trigger Non-model Processes

Let's start out easy. As I mentioned Rest Framework is great at working with models, but it can also be easily used for things other than retrieving and saving models. Say for instance you have an email form on your site for sharing a post. Using AJAX posting of those fields makes for a better experience for your user and keeps them browsing the page longer.

While it's not exactly clear, the APIView allows for this. Most of the documentation for APIView is spent doing model retrievals that are made easier when you start using GenericAPIView and those that inherit from it. However, using the dispatch methods (post, get, delete, patch, put, etc.), just like a Django class-based view, our example above becomes pretty easy.

from rest_framework import views
from rest_framework.response import Response

class ShareView(views.APIView):
    permission_classes = []

    def post(self, request, *args, **kwargs):
        email = request.DATA.get('email', None)
        url = request.DATA.get('url', None)
        if email and url:
            share_url(email, url)
            return Response({"success": True})
            return Response({"success": False})

In these dispatch methods you can easily access the deserialized posted data (via request.DATA), run your custom code (share_url() in this case) and return a serialized response (passing your data to Response). If you're arbitrary code execution applies to a model object, maybe a method call, but doesn't including retrieving or updating that model, simply inherit from the GenericAPIView instead the APIView and use the get_object method of the view.

The one caveat when using APIView is to watch what permissions are applied to the view. Since it is not associated with a model, any permission that expects a model object to be available, like the DjangoModelPermission that is applied by default, will fail, so you should be explicit about permissions.