Saqib Ali Saqib Ali - 3 months ago 11
reST (reStructuredText) Question

How to build a Django REST-Api that returns a custom list of models?

I'm having a great deal of trouble trying build my API using the Django Rest Framework. I have been stuck on the same issue now for several days. I've tried numerous solutions and code-snippets and asked plenty of people but to no avail. I've tried to follow all the instructions in the docs, but to me they are unclear and incomplete. So I'm very desperate for a clear, concise, complete working example to solve me problem.

Now here is my question:

I have been successful at building a simple Django Rest API by following the instructions here. These instructions make it very easy to build an API that returns a list of all instances of a certain model, or a single instance based on a user-provided ID. So, since I have a model named MyObject, I have built an api that returns a list of all the myObjects when you hit the URL /api/myObjects. If I hit the URL /api/myObjects/60, it gives me the myObject with ID==60. So far so good!

But I don't want to do this. I want something a bit more complex. The myObject model has a method called getCustomObjects(). This method itself returns a list of myObjects. When I hit the URL /api/myObjects/60, I want it to return the list produced by calling getCustomObjects() on the myObject with ID==60. This seemingly simple change is causing me a very major headache and I can't figure out how to do it. The reason is that because I want to return a non-standard list of objects, I cannot use the standard way of doing things with a ModelViewSet as described in the docs. When I make the changes that I think should work, I get errors. My current error is:

base_name
argument not specified, and could not automatically determine the name from the viewset, as it does not have a
.model
or
.queryset
attribute.
. All the docs I have read to cure this error say that I need to specify a "base_name" argument. What the value of that base_name argument should be and how I should use it in my URL are very unclear to me. I don't have a good explanation. That's why I'm posting my complete code. If someone can advise me clearly and completely how to fix it, I would be very grateful.

My Route looks like this in myApp's url.py:

from rest_framework import routers
router = routers.DefaultRouter() router.register(r'myObjects/(?P<id>\d+)/?$', views.MyObjectsViewSet)
url(r'^api/', include(router.urls)),


My Model looks like this:

class MyObject(models.Model):
name = models.TextField()


My Serializer looks like this:

class MyObjectSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = MyObject
fields = ('id', 'name',)


My Viewset looks like this:

class MyObjectsViewSet(viewsets.ViewSet):

def retrieve(self,request,pk=None):
queryset = MyObjects.objects.get(pk=pk).customMyObjectList()

if not queryset:
return Response(status=status.HTTP_400_BAD_REQUEST)
else:
serializer = MyObjectSerializer(queryset)
return Response(serializer.data,status=status.HTTP_200_OK)


When I hit /api/myObjects/60/ I get the following error:

`base_name` argument not specified, and could not automatically determine the name from the viewset, as it does not have a `.model` or `.queryset` attribute.

Answer

base_name is used so the router can properly name the URLs. The DefaultRouter you are using uses the model or queryset attributes of the view set. But since you are using viewsets.ViewSet which has neither, the router cannot determine the base name used to name the generated URL patterns (eg, 'myobject-detail' or 'myobject-list')

router.register(r'myObjects', views.MyObjectsViewSet, base_name='myobject')

This will result in creating the following URL pattern: ^myObjects/{pk}/$ with the name: 'myobject-detail'.

Notice the first param to router.register must be r'myObjects' not r'myObjects/(?P<id>\d+)/?$' because the router just needs the prefix and will take care of creating the patterns. To summarize this is an excerpt from DRF docs

There are two mandatory arguments to the register() method:

prefix - The URL prefix to use for this set of routes.

viewset - The viewset class.

Optionally, you may also specify an additional argument:

base_name - The base to use for the URL names that are created. If unset the basename will be automatically generated based on the model or queryset attribute on the viewset, if it has one. Note that if the viewset does not include a model or queryset attribute then you must set base_name when registering the viewset.

See routers docs: www.django-rest-framework.org/api-guide/routers

Comments