Discussion:
rest-framework @action decorator does not seem to support permission_classes attribute.
Del Hyman-Jones
2018-07-30 21:14:43 UTC
Permalink
The example here (
http://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing)
of the documentation suggests that *permission_classes* can be specified as
an attribute to the *@action* decorator but there does not seem to be any
code in the rest-framework to support this. Here is a snippet from the docs:

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

Example Code
The example code I used is here:

views.py
from rest_framework import permissions
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response


class MyPermission(permissions.BasePermission):

def has_permission(self, request, view):
return True


class MyViewSet(viewsets.GenericViewSet):
permission_classes = (permissions.IsAuthenticated,)

@action(detail=True, permission_classes=[MyPermission])
def test(self, request, *args, **kwargs):
return Response(status.HTTP_204_NO_CONTENT)

@action(detail=True)
def test2(self, request, *args, **kwargs):
return Response(status.HTTP_204_NO_CONTENT)


urls.py

urlpatterns = [
path('admin/', admin.site.urls),
url(r'^api-auth/', include('rest_framework.urls')),
url(r'^api/test/$', MyViewSet.as_view(actions={'get': 'test'})),
url(r'^api/test2/$', MyViewSet.as_view(actions={'get': 'test2'})),
]

When tracing the framework code only *IsAuthenticated* is referred to
because it is set in the *MyViewSet* class instance *permission_classes*
variable.* MyPermission.has_permission* is never called.

The action decorator code simply adds *permission_classes* as a variable to
the function object and not to *MyViewSet().permission_classes* as is
required by the framework's *get-permissions* function. (
https://github.com/encode/django-rest-framework/blob/8493990a66e36d5dd4a62742d861b5b6b15cae80/rest_framework/views.py#L276-L280
)

Framework code change suggestion
If the built-in *rest_framework.views.APIView.get_permissions* function was
changed to the following it would automatically support the *permissions_classes
*attribute of *@action* and if that was not specified then default's to
* MyViewSet().permission_classes *in my example.

def get_permissions(self):
func_actions = {
action_details.url_name: action_details.kwargs.get(
'permission_classes'
) for action_details in self.get_extra_actions()
}
perm_classes = func_actions.get(self.action) or self.
permission_classes
return [permission() for permission in perm_classes]


Can anyone tell me if either the documentation is wrong, the framework code
does not match the documentation or I've misunderstood something here?

Assuming I haven't misunderstood, does anyone think that the code change
suggestion is worth mentioning to the DRF developers?

Cheers

Del
--
You received this message because you are subscribed to the Google Groups "Django REST framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-rest-framework+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Xavier Ordoquy
2018-07-31 09:31:30 UTC
Permalink
Hi,

I am not convinced a change is required on DRF.
You are trying to shortcut the router’s work and thus you have some part missing.
If you register your view to a router and trace what it provides to the « as_view » call, you’ll see it passes the @detail arguments to the as_view’s initikwargs on top of the « basename » and « detail » values.
Since you are not using a router, you’ll have to provide those by yourself and have some function that would generate it for you. You should look at how the router deals with it (line: https://github.com/encode/django-rest-framework/blob/0148a9f8dac6730981f4a3d666d8def4570fdae0/rest_framework/routers.py#L205 <https://github.com/encode/django-rest-framework/blob/0148a9f8dac6730981f4a3d666d8def4570fdae0/rest_framework/routers.py#L205>)

Regards,
Xavier,
Linovia.
@action(methods=['post'], detail=True, permission_classes=[IsAdminOrIsSelf])
...
Example Code
views.py
from rest_framework import permissions
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
return True
permission_classes = (permissions.IsAuthenticated,)
@action(detail=True, permission_classes=[MyPermission])
return Response(status.HTTP_204_NO_CONTENT)
@action(detail=True)
return Response(status.HTTP_204_NO_CONTENT)
urls.py
urlpatterns = [
path('admin/', admin.site.urls),
url(r'^api-auth/', include('rest_framework.urls')),
url(r'^api/test/$', MyViewSet.as_view(actions={'get': 'test'})),
url(r'^api/test2/$', MyViewSet.as_view(actions={'get': 'test2'})),
]
When tracing the framework code only IsAuthenticated is referred to because it is set in the MyViewSet class instance permission_classes variable. MyPermission.has_permission is never called.
The action decorator code simply adds permission_classes as a variable to the function object and not to MyViewSet().permission_classes as is required by the framework's get-permissions function. (https://github.com/encode/django-rest-framework/blob/8493990a66e36d5dd4a62742d861b5b6b15cae80/rest_framework/views.py#L276-L280 <https://github.com/encode/django-rest-framework/blob/8493990a66e36d5dd4a62742d861b5b6b15cae80/rest_framework/views.py#L276-L280>)
Framework code change suggestion
func_actions = {
action_details.url_name: action_details.kwargs.get(
'permission_classes'
) for action_details in self.get_extra_actions()
}
perm_classes = func_actions.get(self.action) or self.permission_classes
return [permission() for permission in perm_classes]
Can anyone tell me if either the documentation is wrong, the framework code does not match the documentation or I've misunderstood something here?
Assuming I haven't misunderstood, does anyone think that the code change suggestion is worth mentioning to the DRF developers?
Cheers
Del
--
You received this message because you are subscribed to the Google Groups "Django REST framework" group.
For more options, visit https://groups.google.com/d/optout <https://groups.google.com/d/optout>.
--
You received this message because you are subscribed to the Google Groups "Django REST framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-rest-framework+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Del Hyman-Jones
2018-07-31 16:01:41 UTC
Permalink
Hi Xavier

Thanks for your reply. At the back of my mind something was telling me that
I was missing something and the Router was the piece that was missing!

Thanks again for your input on this.

Del
--
You received this message because you are subscribed to the Google Groups "Django REST framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-rest-framework+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Loading...