Django REST framework ViewSet when you don’t have a Model
This is a topic I’ve seen on a regular basis and spoke about in a few meetups. I think it’s now time to have something written about.
Django REST framework ViewSet is part of the resource representation. It’s a really nice part of DRF since it helps keep things separated. The documentation focuses a lot on the ModelViewSet since there are a few automated things for it while regular ViewSet is mostly up to the developer. Here’s how to get a non model based ViewSet playing nice with the browsable API.
The full example is available at https://github.com/linovia/drf-demo. This example can be found in the drf_demo/non_model.
In this article I’m going to expose a Task resource. This is a pure Python object as shown by its definition. It is no more than a container for a few data.
As for the dataset and for the sake of simplicity, I use a dictionary of Task. In real life this would use different data sources such as a NoSQL store, some caching system, a LDAP, some files…
The API performs the usual CRUD operations on the items of the list. I choosed to use a dictionary here in order to keep the code as simple as possible.
A model-less serializer is pretty straight forward. It inherits Serializer — as opposed to ModelSerializer — and describes the fields. In order to make it writable it has a create and an update member.
create simply passes the validated data to the Task’s initialization.
update pushes the validated_data values to the given instance. Note that update should not assume all the fields are available. This helps to deal with partial updates (PATCH requests).
The ViewSet is the second part of our resource representation. Let’s expose the CRUD operations on the resource:
- list: list all the available resources.
- create: create a new resource.
- retrieve: show a single given resource.
- update: updates a single resource.
- partial_update: updates a single resource with a partial set of input.
- destroy: removes the given resource.
We need to link each action to the associated operation. For example the list translates into instanciating a serializer with the list of our tasks, mentioning we’re working with a list (many=True) and return the serialized data.
The code here is a simplified model-less version of the Django REST framework ListModelMixin from rest_framework.mixins.
If you want to play with the browsable API you’ll want to add the serializer_class to the ViewSet in order to have a nice creation / update form. It would work without this but you’ll be limited to a text area in which you can paste raw JSON content.
In order to expose our ViewSet we’ll need to register it to the routers. It’s mostly the same as with regular ViewSet plus an extra explicit base_name argument. The base_name argument helps build url names. Routers will generate two urls for each ViewSet:
- an url for listing all the resources and creating new ones
- an url to interact with a single resource — view, update, delete
This translates into
The url to list all the resources is “api/tasks” (taken from r’tasks’) and its name is “tasks-list” (taken from base_name with trailing “-list”). The url for a single resource is “api/tasks/<pk>” and its name is “tasks-details” (taken from base_name with a trailing “-details”).
When dealing with ModelViewSet, base_name defaults to the Model’s name. This is usually why you don’t have to add the argument with ModelViewSet. Since we’re not using a Model, it has to be explicitly given.
At this point your own Model-less API is running with Django REST framework. It takes advantage of the browsable API as shown by the screenshot.