Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django REST: Show details and list on 1 url

There is a Django project that uses a REST API.

Most of the url-patterns follow the same paradigm:

/foo_config/1

Basically /list-view/details-view

But there is another type of config - which can only have 1 object, so showing it like /bar_config/1 is confusing, because user might expect #2 and #3, etc.

I would like to 'map' bar_config details and list together using simple /bar_config.

Tried using @detail_route, but it doesn't work:

@detail_route(methods=['put'])
def put_config(self, request):
  ...

How to achieve my goal?

UPDATE: after trying to play with detail_route and Ivan Semochkin answer - still no luck. I will provide more details on what I have:

/accounts/foo_account/bar_config/ <-- list view /accounts/foo_account/bar_config/1 <-- details view

Accounts defined as:

router = routers.DefaultRouter()
router.register('accounts', AccountViewSet)

The goal is to:

  1. Show bar_config/1 on bar_config (PUT on LIST page);
  2. Prohibit/hide bar_config/1;
  3. Allow to view and update info, prohibit to create/delete;

I will summarize: I need to change the way a sub-link works in the existing project, that already uses DefaultRouter for all its patterns. This sub-link maps singleton-model. I would like to show update (PUT) data on the list (GET) page.

like image 256
0leg Avatar asked Mar 11 '23 07:03

0leg


1 Answers

Why don't you just override list method?

class ConfigViewSet(viewsets.ReadOnlyModelViewSet):
    
    queryset = Config.objects.all()  
    serializer_class = ConfigSerializer

    def retrieve(self, request, *args, **kwargs):
        return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
   
    def list(self, request, *args, **kwargs):
        instance = self.get_queryset().first()
        # using 'first' will retrieve first instance
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

It will work fine it you don't only show that model. If you want to provide some CRUD, then inherit from ModelViewSet and override delete and update methods.
Update for comment
Another way to do it - create a custom router, with this router you don't need to use list, use retrieve instead

from rest_framework.routers import Route, SimpleRouter

class CustomRouter(SimpleRouter):
"""
A router for update and retrieve without lookup field.
"""
    routes = [
       Route(
            url=r'^{prefix}/$',
            mapping={'put': 'update'},
            name='{basename}-update',
            initkwargs={'suffix': 'Update'}
        ),
        Route(
            url=r'^{prefix}/$',
            mapping={'get': 'retrieve'},
            name='{basename}-detail',
            initkwargs={'suffix': 'Detail'}
        ),
    ]  

ViewSet:

class ConfigViewSet(viewsets.ModelViewSet):

    queryset = Config.objects.all()  
    serializer_class = ConfigSerializer

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_queryset().first()
        # using 'first' will retrieve first instance
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

    def update(self, request, *args, **kwargs):
        instance = self.get_queryset().first()
        serializer = self.get_serializer(instance, data=request.data)
        # you could also provide `partial=True` in serializer for partial update
        if serializer.is_valid():
            serializer.save() 
            return Response(serializer.data)
        else:
            return Response(serializer.errors)

urls:

router = CustomRouter()
router.register('config', ConfigViewSet)
urlpatterns = router.urls
like image 124
Ivan Semochkin Avatar answered Mar 27 '23 04:03

Ivan Semochkin