What base_name parameter do I need in my route to make this Django API work?
Asked Answered
B

8

47

I am building a Django application that exposes a REST API by which users can query my application's models. I'm following the instructions here.

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.

I understand from here that I need a base_name parameter on my route. But from the docs, it is unclear to me what that value of that base_name parameter should be. Can someone please tell me what the route should look like with the base_name?

Beam answered 27/2, 2014 at 23:38 Comment(1)
A similar question (made also by @Saquib Ali) was answered here addressing the same problem.Anabas
V
48

Try doing this in your urls.py. The third parameter 'Person' can be anything you want.

router.register(r'person/food', views.PersonViewSet, 'Person')
Visitor answered 28/2, 2014 at 18:7 Comment(5)
and then how what should I type into my browser as the URL to hit that API?Beam
@SaqibAli it would be 127.0.0.1:8000/person/food.Visitor
@EricLee can you explain why is this happening? Thank youTedie
@Tedie The default router uses the queryset defined in the serializer to determine the basename. The basename designates the start of the urls generated by the router. If the queryset is not set as a ViewSet attribute, the router throws up its hands in protest without trying to find queryset in functions. Specifying 'Person' means the internal view_name's will start with 'Person', but the url's are still specified by the first argument Check this out for more: django-rest-framework.org/api-guide/routers/#usageBalance
This answer doesnt explain anythingMixed
P
40

Let me explain, why we need an base_name in the first place and then let's go into the possible value of base_name.

If you ever used the Django urls without the rest-framework (DRF) before, you would've specified it like this:

urlpatterns = [
    url(r'myObjects/(?P<id>\d+)/?$', views.MyObjectsListView.as_view(), name='myobject-list'),
    url(r'myObjects/(?P<id>\d+)/?$', views.MyObjectsDetailView.as_view(), name='myobject-detail'),
]

Here, if you see, there is a name parameter which used to identify the url in the current namespace (which is app).

This is exactly what django-rest-framework trying to do automatically, since the drf knows whether the view is list or detail (because of viewset). it just needs to append some prefix to differentiate the urls. That's the purpose of base_name (prefix).

In most scenario, you can give the url or resource name as base_name. In your case, base_name=myobject. DRF will generate base_name + view_type as the name parameter like myobject_list & myobject_detail.

Note: Usually, base_name will be automatically obtained from the queryset field (from view), since it's same for all view types in a viewset. But if you specify, get_queryset method instead of queryset, it possibly means you've different queryset for different view types (like list, detail). So, DRF will ask you to specify a common base_name for all the view types for a resource.

Postage answered 17/5, 2018 at 1:40 Comment(0)
E
13

Maybe you just need to set the base_name parameter for your router with the name of the object: MyObject, in your case.

router.register(r'myObjects/(?P<id>\d+)/?$', views.MyObjectsViewSet, base_name="MyObject")

http://www.django-rest-framework.org/api-guide/routers/#Usage

Errol answered 22/1, 2015 at 8:48 Comment(2)
Stupidly strange how the docs use basename rather than base_name.Crinkly
It appears the base_name argument was renamed to basename at some point between versions 3.10.3 and 3.11.0Milena
G
4

this is a useful answer, read for the details.

tl;dr

It is used as the base name of the generated URL patterns (e.g., 'myobject-detail' or 'myobject-list').

Globular answered 8/2, 2017 at 14:7 Comment(1)
but why is it necessary to have base name for generated url patterns?Mixed
P
2

An alternative solution might be to use a ModelViewSet which will derive the basename automatically from the model.

Just make sure and tell it which model to use:

Because ModelViewSet extends GenericAPIView, you'll normally need to provide at least the queryset and serializer_class attributes, or the model attribute shortcut.

Postal answered 27/2, 2014 at 23:46 Comment(1)
This is really hard and confusing. Can you just tell me based on my code above what that parameter base_name should be? I don't want to use MovelViewSet because I am not using querysets. I'm doing something more complicated by calling customMyObjectsList().Beam
R
2

simply mention this,

queryset = MyObjects.objects.all()

like this,

class MyObjectsViewSet(viewsets.ViewSet):
    queryset = MyObjects.objects.all()

in your corresponding Viewset in views.py instead of mentioning under

def retrieve()...

its worked for me :)

Retriever answered 19/2, 2016 at 13:26 Comment(0)
D
0

Say you have two functions in your views.py to query a bunch of employees. One to query just the name(employeenameviewset) and other to query addresses also(employeeinfoviewset). Then in your urls.py, add like so:

router.register(r'employee_names', views.employeenameviewset, basename="employees")
router.register(r'employee_details', views.employeeinfoviewset, basename="employees")

Using the same basename, the rest of the url will be created automatically by Django like it does in case of URL Conf. In fact this is based on URLConf.

Devinne answered 1/8, 2020 at 8:51 Comment(0)
C
0

DRF routers register a tuple of (prefix, viewset, basename)

  • The prefix is what we want to append to the actual URL
  • The viewset is the ViewSet for what we want to configure several urls (list, detail, create, ....)
  • The basename is the first part of a string that we would use in the reverse() function.

As it is explained in the documentation, the basename parameter will be automatically generated from the name of the model in the queryset attribute of the ViewSet. So having this register and Viewset:

class UserViewSet(ModelViewSet):
   queryset = User.objects.all()

...

router.register("users", UserViewSet)

will generate an automatic user as a basename.

What documentation lacks is an explanation on what is the actual use of the basename, and that is for the reverse() method.

What is reverse method?

Is a useful method from rest_framework.reverse module that returns a fully qualified url from a string.

>>> reverse('user-list')
'/users/'
>>> reverse('user-detail', kwargs={"pk": 1})
'/users/1/'

You can see that the first part of the view name is the basename, then comes the specific view that you want to retrieve.

Clef answered 18/7 at 17:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.