3

Here's the viewset

class MobileDeviceViewset(ModelViewSet):
    @action(
        methods=['post', 'put', 'patch'],
        url_path='token',
        detail=True,
    )
    def update_token(self, request, *args, **kwargs) -> Response:
        ...

    @action(
        methods=['get'],
        url_path='token',
        detail=True,
    )
    def get_token(self, request, *args, **kwargs) -> Response:
        ...

So what I want to do here is have an endpoint /token/ that the app will send a GET request to to check if there is a token, and get it if there is. I also want to use that same /token/ endpoint to send an updated token to. What happens currently is that I get an error telling me that the POST/PATCH/PUT methods are not allowed on that endpoint, so it appears to only be recognizing the get_token method. The token object here is actually a ManyToMany through model called MobileDeviceUser, so I'm not just trying to update a field on the MobileDevice object.

2 Answers 2

3

Since your url_path and detail are the same in both cases, why do you want two separate methods in your views??
Anyway I would recommend this way,

class MobileDeviceViewset(ModelViewSet):
    # your code

    @action(methods=['get', 'post', 'put', 'patch'], url_path='token', detail=True, )
    def my_action(self, request, *args, **kwargs):
        if request.method == 'GET':
            return self.get_token(request, *args, **kwargs)
        else:
            return self.update_token(request, *args, **kwargs)

    def update_token(self, request, *args, **kwargs):
        return Response("update token response--{}".format(request.method))

    def get_token(self, request, *args, **kwargs):
        return Response("update token response--{}".format(request.method))

Then you have to make some changes in your URL config,

from django.urls import path
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('mysample', MobileDeviceViewset, base_name='mobile-device')
actions = {
    "get": "my_action",
    "post": "my_action",
    "put": "my_action",
    "patch": "my_action"
}
urlpatterns = [
                  path('mysample/<pk>/token/', MobileDeviceViewset.as_view(actions=actions))

              ] + router.urls

Hence, your URL will some something like, ..../mysample/3/token/

NOTE
This solution tested under Python 3.6, Django==2.1 and DRF==3.8.2


UPDATE

Why Method Not Allowed error?

When a request comes to Django, it searches for the patterns in the URL configs and sends the request to the corresponding view if a match occurs.
In your case, you've defined two views (yes..it's actions though) with the same URL (as below,).

actions = {
    "post": "update_token",
    "put": "update_token",
    "patch": "update_token"
}

urlpatterns = [
                  path('mysample/<pk>/token/', MobileDeviceViewset.as_view(actions={"get": "get_token"})),
                  path('mysample/<pk>/token/', MobileDeviceViewset.as_view(actions=actions))

              ] + router.urls


In this case, a request comes (let it be a HTTP POST) and the URL dispatcher redirects to the first view which satisfies the URL path. So, the POST request goes into the get_token method, but, it's only allowed for GET method


What is the possible solution?

Method-1:
As I described in the top, use a common action and distingush the HTTP METHODS and call appropriate methods
Method-2:
Use different URL path for both actions, as

actions = {
    "post": "my_action",
    "put": "my_action",
    "patch": "my_action"
}
urlpatterns = [
                  path('mysample/<pk>/get-token/', MobileDeviceViewset.as_view(actions={"get": "get_token"})),
                  path('mysample/<pk>/update-token/', MobileDeviceViewset.as_view(actions=actions))

              ] + router.urls
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks, while this achieves the same behavior, it's not what I was looking for. I essentially want it to redirect to a nested viewset, but using drf-nested-routers isn't doing what I want. I can achieve the same behavior I want by hacking some stuff together, but I was hoping for a built-in manner that I was missing.
So I want /device/<device_id>/ to give me the device with that ID, and then I want /device/<device_id>/token/ to behave like ModelViewSet. I think it may actually be possible somehow with DRF's HyperlinkedSerializerField, but I'll have to play with it later. For now I've just gone with a simpler route of having /get-token/ and /update-token/
You'd said you need same url path...and now you are saying two paths, which are /get-token/ ,/update-token/
I never said I need the same URL path. I just would have preferred it. My point is that if I can't implement it in the desired method on the back-end, then I'll just go with two endpoints. The front-end developers don't care.
3

you can do this via:

class MobileDeviceViewset(ModelViewSet):    

    @action(
        methods=['get'],
        url_path='token',
        url_name='token',
        detail=True,
    )
    def get_token(self, request, *args, **kwargs) -> Response:
        ...

    @get_token.mapping.post
    @get_token.mapping.put
    @get_token.mapping.patch
    def update_token(self, request, *args, **kwargs) -> Response:
        ...

https://www.django-rest-framework.org/api-guide/viewsets/#routing-additional-http-methods-for-extra-actions

Comments

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.