Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django REST Framework - Define extra arguments using the @action decorator

I'm wondering if would be possible to define extra arguments using the action decorator:

class UserViewSet(viewsets.ModelViewSet):
    """
    A viewset that provides the standard actions
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @action(methods=['post'], detail=True)
    def follow(self, request, pk=None):
        user = self.get_object()
        target_user = ???
        Follow.objects.create(user=user, target=target_user)
        return Response(status=status.HTTP_204_NO_CONTENT)

What I want to achieve is matching the following path: api/users/{id}/follow/{target_id}

The follow action will be used to let an user with ID id following another user with ID target_id.

UPDATE:

Since it seems like it's not possible to pass extra args, right now I'm POSTing data as a list of user IDs to be followed:

The serializer to validate data:

class FollowSerializer(serializers.ModelSerializer):
    user_ids = serializers.ListField(child=serializers.IntegerField(min_value=1), required=False, help_text='User target IDs')

The action:

    @action(detail=True, methods=['post'])
    def follow(self, request, pk=None):
        user = self.get_object()
        serializer = FollowSerializer(data=request.data)
        if serializer.is_valid():
            serializer.data['user_ids']
            for user_id in user_ids:
                target_user = User.objects.get(pk=user_id)
                Follow.objects.create(user=user, target=target_user)
            return Response(status=status.HTTP_204_NO_CONTENT)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

The problem with my solution is that on the auto-generated doc page I'm seeing the form with the UserSerializer fields.

API Doc Page

UPDATE 2:

The following trick let the auto-generated doc page shows the right form:

class UserViewSet(viewsets.ModelViewSet):
    """
    A viewset that provides the standard actions
    """
    queryset = User.objects.all()

    def get_serializer_class(self):
        if self.action == 'follow':
            return FollowSerializer
        else:
            return UserSerializer

    @action(methods=['post'], detail=True)
    def follow(self, request, pk=None):
        ...

API Doc Page - Correct Form

like image 830
Fabio Avatar asked Aug 13 '18 13:08

Fabio


1 Answers

You could pass target_id through url as,api/users/{id}/follow/{target_id} , but you have to change the views as,

class UserViewSet(viewsets.ModelViewSet):
    """
    A viewset that provides the standard actions
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @action(methods=['post'], detail=True)
    def follow(self, request, *args, **kwargs):
        user = self.get_object()
        target_user = int(kwargs['target_id'])
        Follow.objects.create(user=user, target=target_user)
        return Response(status=status.HTTP_204_NO_CONTENT)

and define a seperate path() in urls.py as,

urlpatterns = [
                  path('users/<int:pk>/follow/<int:target_id>/', UserViewSet.as_view({"post": "follow"}))

              ]
like image 117
JPG Avatar answered Oct 05 '22 19:10

JPG