6

Typically, in a DRF Viewset you might do something like this:

class FooViewSet(viewsets.ViewSet):
    """
    Foo-related viewsets.
    """
    permission_classes = [IsAuthenticated,]

    def list(self, request):
        """
        A list of foo objects.
        """
        context = {'request': self.request}
        queryset = Foo.objects.all()
        serializer = FooSerializer(queryset, many=True, context=context)
        return Response(serializer.data)

    def retrieve(self, request, pk=None):
        """
        Get one publicly available Foo item.
        """
        context = {'request': self.request}
        queryset = Foo.objects.all()
        store_object = get_object_or_404(queryset, pk=pk)
        serializer = FooSerializer(store_object, context=context)
        return Response(serializer.data)

This works fine, and respectively correlates to:

GET /foo and GET /foo/<pk>. However, the last endpoint I need is POST /foo/<pk>. The problem here is that providing a create method to the views typically will be routed to POST /foo. Is there anything neat and elegant I can do from the ViewSet itself? Or is the only option basically to route POST /foo/<pk> to a specific one-off view?

1
  • Did my answer help to resolve your issue at all? Commented Feb 25, 2020 at 12:11

1 Answer 1

5

So, I would say in your urls.py for the REST endpoints you need:

urlpatterns = [
    path(
        '/foo',
        viewsets.FooViewSet.as_view({'post': 'create'}),
        name='Create Foo',
    ),
    path(
        '/foo',
        viewsets.FooViewSet.as_view({'get': 'list'}),
        name='List Foo',
    )
    path(
        '/foo/<pk>',
        viewsets.FooViewSet.as_view({'get': 'retrieve'}),
        name='Retrieve Foo',
    )
]

It is convention to only patch (partial update) or put (update) to a /foo/<pk> endpoint.

Therefore, post to the base /foo endpoint, with a JSON deserialized representation of the object you want to create (minus the pk, let the DB create the pk on the fly).

If you want to update foo, then simply add:

urlpatterns = [
    ...
    path(
        '/foo/<pk>',
        viewsets.FooViewSet.as_view({'put': 'update'}),
        name='Update Foo',
    ),
    path(
        '/foo/<pk>',
        viewsets.FooViewSet.as_view({'patch': 'partial_update'}),
        name='Partially Update Foo',
    )
]

This would correspond to your FooViewSet as the following, as the most basic example as:

class FooViewSet(viewsets.ViewSet):

    def list(self, request):
        pass

    def create(self, request):
        pass

    def retrieve(self, request, pk=None):
        pass

    def update(self, request, pk=None):
        pass

    def partial_update(self, request, pk=None):
        pass

    def destroy(self, request, pk=None):
        pass

Replacing each of the needed endpoints with the code you desire. Leaving in pass will disallow the method as well, which is handy if, for example, you wish to disable the destroy action on your API.

Addendum: It's worth adding you can also do the following:

urlpatterns = [
    path(
        '/foo',
        viewsets.FooViewSet.as_view({'post': 'custom_post_action'}),
        name='Create Foo',
    ),

With the corresponding in your FooViewSet():

class FooViewSet(viewsets.ViewSet):
    ...

    @action(methods=['post'], detail=False, permission_classes=[SomePermissionClass], url_path='?', url_name='?')
    def custom_post_action(self, request):
        pass

If you wish, put I feel it may be bad practise and not truly representative of the "RESTful" principles, but who gives a CRUD if it's what you need?

Sign up to request clarification or add additional context in comments.

7 Comments

This is interesting. So what I'm trying to do here is not to create an object, but to add an object of another type to the user's list. So we have /notes for example, I want to add this to the user's list at POST /users/notes/<noteiD>. Is this not a good way to do this ?
Although there is some linking between users and notes, I would probably organise it something more like /users/<user_pk>/notes to return all notes for a given User...so it sort of has hierarchical structure..then you can pass in the pk for the User to that endpoint...do you want me to add maybe a potential how to do this in my answer?
So in this case, these notes are all generic objects that may not necessarily be created by the user. Let's say I want to add a generic note (made by someone else) at notes/<note_id> to the current user's list of notes. How would I structure that route?
Hmmmm, so I would have a ownership relation for the notes....such that User A can create them. So a ForeignKey relationship between Note and User called owner. And then...potentially, a ManyToMany relationship between Note and User called somwething like for...
I would then structure the API such that you could have an endpoint just for users/<user_pk>/notes/<note_pk> ... but this isn't really RESTful in design as you have two pk links... as you will need to create quite the custom endpoint ... I would run with /users/<user_pk> which will have as the one of the fields a serialized list of note ids, then you can provide permissions on a note by note basis for the user to then view them. So custom permission class will be need UserCanViewNote, such that if you pass in the pk the currently logged in user can view that note...else 404 status.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.