Question: Django REST: map 'update' to 'list' using CustomRouter

Question

Django REST: map 'update' to 'list' using CustomRouter

Answers 1
Added at 2016-12-27 14:12
Tags
Question

Problem explanation:

There is a Django project using REST API. The router is defined as:

router = routers.DefaultRouter()

The Accounts router:

router.register('accounts', AccountViewSet)
accounts_router = NestedSimpleRouter(router, r'accounts')

There are other routers under accounts_router which use standard paradigm (as described on Django Reference), e.g.:

URL pattern: ^accounts/$ Name: 'account-list'
URL pattern: ^accounts/{pk}/$ Name: 'account-detail'

Problem:

Now there is object which has only 1 instance - singleton_foo. Showing its details view as /accounts/foo_acc/singleton_foo/1 would be a bad idea, since user may expect #2, #3, etc.

Goal:

I want to use custom router to map '/accounts/foo_acc/singleton_foo/1' to '/accounts/foo_acc/singleton_foo'. Basically, I want to show and update singleton_foo details (update) in the list view (list)

I have tried using something like:

class SingletonNestedRouter(NestedSimpleRouter):
    routes = [
        Route(
            url=r'^{prefix}/$',
            mapping={'get': 'list'},
            name='{basename}-list',
            initkwargs={'suffix': 'List'}
        ),
        Route(
            url=r'^{prefix}/{pk}/$',
            mapping={'put': 'retrieve'},
            name='{basename}-retrieve',
            initkwargs={'suffix': 'Retrieve'}
        ),
    ]

And attach the router to accounts:

accounts_router.register('singleton_foo', SingletonFooViewSet, 'singletonfoo')
ConfigNestedRouter(domains_router, singleton_foo', lookup='singleton_foo')

Any suggestions?

Answers
nr: #1 dodano: 2016-12-27 17:12

If this is possible in your project, you can achieve same behavior by overriding some of a viewset's methods. The core things here:

  • getting only first item in the queryset (with "[:1]" in get_queryset),
  • deny creation of a new item if one is already exists (in create()),
  • rewrite get_object() method so it won't raise exception when we haven't passed value for the lookup_field.

views.py

class SingletonView(viewsets.ModelViewSet):

    serializer_class = SingletonSerializer

    def get_queryset(self):
        return SingletonModel.objects.all()[:1]

    def create(self, request, *args, **kwargs):
        if not self.get_queryset().exists():
            return super(SingletonView, self).create(request, *args, **kwargs)
        else:
            return Response({
                'message': 'Already exists and can be only one!'
            }, status=status.HTTP_400_BAD_REQUEST)

    def get_object(self):
        queryset = self.filter_queryset(self.get_queryset())
        obj = shortcuts.get_object_or_404(queryset)

        # May raise a permission denied
        self.check_object_permissions(self.request, obj)

        return obj

urls.py

urlpatterns = [
    url(r'^$', SingletonView.as_view({'get':'list', 'post': 'create', 'delete': 'destroy', 'put': 'update'})),
]

Now you can access all methods (GET, PUT, POST, DELETE) through the same url without pk with "only-one-item" control.

Source Show
◀ Wstecz