user7304253
user7304253

Reputation:

Multiple nested resources with multiple methods are not displayed in Django Rest Framework Documentation

I wonder why Django REST Framework build-in documentation doesn't display methods for a User. I have only available list, create, read, update for these URLs:

url(r'^users$', views.UserList.as_view()),
url(r'^users/(?P<user_id>\w+)$', views.UserDetail.as_view()),

views.py:

@permission_classes([CustomPermission])
class UserList(GenericAPIView):

    """
    get: Return all users.
    post: Create a user.
    """

    serializer_class = UserSerializer

    def get(self, request):

        users = User.objects.all()
        serializer = UserSerializer(users, many=True)
        return Response(serializer.data)

    def post(self, request):
        serializer = UserSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


@permission_classes([UserPermission])
class UserDetail(GenericAPIView):

    """
    get: Return user by ID.
    put: Update user by ID.
    delete: Delete user by ID.
    """

    serializer_class = UserSerializer

    def get(self, request, user_id):

        try:
            user = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)
        serializer = UserSerializer(user)
        return Response(serializer.data)

    def put(self, request, user_id):

        try:
            user = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)
        serializer = UserSerializer(user, data=request.data)
        if serializer.is_valid():
          serializer.save()
          return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, user_id):

        try:
            user = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)
        user.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

However example shown below is not visible in build-in documentation. I have also Swagger documentation in the project and everything is displayed properly.

urls.py:

url(r'^users/(?P<user_id>[0-9]+)/object$', views.UserObject.as_view()),

views.py:

@permission_classes([UserPermission])
class UserObject(GenericAPIView):

    """
    post: Create a user object by his ID.
    get: Return a user object by his ID.
    put: Update a user object by his ID.
    delete: Delete a user object by his ID.
    """

    serializer_class = ObjectSerializer

    def post(self, request, user_id):

        try:
            Object.objects.get(user=user_id)
            return Response(status=status.HTTP_403_FORBIDDEN)


        except Object.DoesNotExist:
            serializer = ObjectSerializer(data=request.data)
            serializer.fields['user'].required = False
            if serializer.is_valid():
                serializer.save(user_id=user_id)
                return Response(serializer.data, status=status.HTTP_201_CREATED)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


    def get(self, request, user_id):
        try:
            object = Object.objects.get(user=user_id)
        except Object.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)
        serializer = ObjectSerializer(object)
        return Response(serializer.data)

    def put(self, request, user_id):
        try:
            object = Object.objects.get(user=user_id)
        except Object.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)
        serializer = ObjectSerializer(object, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, user_id):
        try:
            object = Object.objects.get(user=user_id)
        except Object.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)
        object.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

There should be visible path users/{user_id}/object Any idea why is not?

enter image description here

Upvotes: 1

Views: 1326

Answers (1)

Willemoes
Willemoes

Reputation: 6367

Edit 2017-08-19

I've made a PR with the fix which has already been merged. So may be try to get latest version.

Edit 2017-08-13

This is a bug with DRF default documentations, where extra actions with more than one method are not displayed in the docs.

Solution: use swagger

Original

I tried reproducing it, looks like there is a bug in the coreapi from django-rest-framework.

I've tried with the doc generator swagger for rest-framework and it looks fine.

There is a bug in the url if you remove object from users/{user_id}/object it works. If you try for example xsers/{user_id}/ it'll work.

swagger example

You could change the design approach using a ViewSet. ViewSet provides actions instead of mapping directly to the method. It's another level of abstraction, usually clearer.

class UserViewSet(viewsets.ViewSet):
    """
    retrieve:
    Return the given user. 

    list:
    Return a list of all the existing users.

    create:
    Create a new user instance.

    update:
    Update a user.
    """

    serializer_class = UserSerializer

    def list(self, request):
        # Here you should put the code for GET user/
        pass

    def create(self, request):
        # Here you should put the code for POST user/
        pass

    def retrieve(self, request, pk=None):
        # Here you should put the code for RETRIEVE user/{pk}
        pass

    def update(self, request, pk=None):
        # Here you should put the code for UPDATE user/{pk}
        pass

    @detail_route(methods=['get'])
    def objects(self, request, pk=None):
        if request.method == 'GET'
        ....

And in your urls

from myapp.views import UserViewSet
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r'users', UserViewSet)
urlpatterns = router.urls

More info http://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing

Upvotes: 1

Related Questions