By default, when you hook up a model to Django REST Framework and run a query in JSON format, what you get is a list. E.g.
For GET localhost:8000/api/mymodel/
[
{"id": 1, "name": "Foo"},
{"id": 2, "name": "Bar"},
{"id": 3, "name": "Baz"}
]
This isn't great because there's no good way to include other auxiliary data points that are relevant to this query. In Elasticsearch you get something like this:
{
"took": 106,
"timed_out": false,
"_shards": {},
"hits": {
"total": 0,
"hits": [],
"max_score": 1
}
}
Another key is that perhaps today you can't think of any immediate reason why you want to include some additonal meta data about the query, but perhaps some day you will.
The way to solve this in Django REST Framework is to override the list
function in your Viewset classes.
Before
# views.py
# views.py
from rest_framework import viewsets
class BlogpostViewSet(viewsets.ModelViewSet):
queryset = Blogpost.objects.all().order_by('date')
serializer_class = serializers.BlogpostSerializer
After
# views.py
from rest_framework import viewsets
class BlogpostViewSet(viewsets.ModelViewSet):
queryset = Blogpost.objects.all().order_by('date')
serializer_class = serializers.BlogpostSerializer
def list(self, request, *args, **kwargs):
response = super().list(request, *args, **kwargs)
# Where the magic happens!
return response
Now, to re-wrap that, the response.data
is a OrderedDict which you can change. Here's one way to do it:
# views.py
from rest_framework import viewsets
class BlogpostViewSet(viewsets.ModelViewSet):
queryset = Blogpost.objects.all().order_by('date')
serializer_class = serializers.BlogpostSerializer
def list(self, request, *args, **kwargs):
response = super().list(request, *args, **kwargs)
response.data = {
'hits': response.data,
}
return response
And if you want to do the same the "detail API" where you retrieve a single model instance, you can add an override to the retrieve
method:
def retrieve(self, request, *args, **kwargs):
response = super().retrieve(request, *args, **kwargs)
response.data = {
'hit': response.data,
}
return response
That's it. Perhaps it's personal preference but if you, like me, prefers this style, this is how you do it. I like namespacing things instead of dealing with raw lists.
"Namespaces are one honking great idea -- let's do more of those!"
From import this
Note! This works equally when you enable pagination. Enabling pagination immediately changes the main result from a list to a dictionary. I.e. Instead of...
[
{"id": 1, "name": "Foo"},
{"id": 2, "name": "Bar"},
{"id": 3, "name": "Baz"}
]
you now get...
{
"count": 3,
"next": null,
"previous": null,
"items": [
{"id": 1, "name": "Foo"},
{"id": 2, "name": "Bar"},
{"id": 3, "name": "Baz"}
]
}
So if you apply the "trick" mentioned in this blog post you end up with...:
{
"hits": {
"count": 3,
"next": null,
"previous": null,
"items": [
{"id": 1, "name": "Foo"},
{"id": 2, "name": "Bar"},
{"id": 3, "name": "Baz"}
]
}
}