1

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

4
  • Hey, I don't understand quite well which url's are working and which ones are not. Is UserObject not working? Do you have any other one working? Have you tried documenting exactly as the doc say: django-rest-framework.org/topics/documenting-your-api/… Commented Aug 11, 2017 at 9:29
  • Are you saying that when you visit your Swagger documentation page everything is documented as expected, but when you visit an endpoint itself, you don't see the DRF GUI? Or something else? Commented Aug 11, 2017 at 12:54
  • @Willemoes there is a problem only with url(r'^users/(?P<user_id>[0-9]+)/object$', views.UserObject.as_view()), The rest works properly. Commented Aug 11, 2017 at 13:47
  • @YPCrumble I don't see only url(r'^users/(?P<user_id>[0-9]+)/object$', views.UserObject.as_view()), The rest is visible as on the screenshot in updated question. Commented Aug 11, 2017 at 13:50

1 Answer 1

1

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

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

5 Comments

For now try changing the doc generator tool, instead of using the drf default one, try using swagger, I've provided the link up there, it says how to install and configure, quite straightforward
I have Swagger documentation now but I would like to have DRF, too. The only idea that comes to my mind is to try create one view for all paths that begin with /users
If you ask me... I'd use a ViewSet, where you define list (for GET user/), create (for POST user/) , retrieve (for a GET user/{pk}) and update (for PATCH user/{pk}, and the object in the last url is an action inside the user resource, for that you should define a detail_route check here how to use django-rest-framework.org/api-guide/viewsets/…, with that, the docs would be generated correctly
I tried to do it using ViewSets unfortunately without results. I have never used it before so I'm not sure how can I achieve my goal.

Your Answer

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