From 7203a8bfc7b94da8726137fdb997db31991e8a05 Mon Sep 17 00:00:00 2001 From: Masse Date: Tue, 8 May 2018 19:15:55 -0400 Subject: [PATCH 1/2] Update viewset_mixins.py to work with DRF >= 3.8 routing decorators and more restrictive dynamic route semantics. --- drf_fsm_transitions/viewset_mixins.py | 36 ++++++++++++++++++--------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/drf_fsm_transitions/viewset_mixins.py b/drf_fsm_transitions/viewset_mixins.py index a1871ba..3410f97 100644 --- a/drf_fsm_transitions/viewset_mixins.py +++ b/drf_fsm_transitions/viewset_mixins.py @@ -1,45 +1,57 @@ -from rest_framework.decorators import detail_route +'''Heavily inspired by: https://github.com/jacobh/drf-fsm-transitions + + Modified to work with DRF >= 3.8 routing semantics +''' +from django_fsm import can_proceed +from rest_framework import exceptions +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response -def get_transition_viewset_method(transition_name, **kwargs): +def get_transition_viewset_method(transition_name, url_name=None, **kwargs): ''' - Create a viewset method for the provided `transition_name` + Create a viewset method for the provided `transition_name`. Requires DRF >= 3.8 ''' - @detail_route(methods=['post'], **kwargs) + @action(detail=True, methods=['post'], permission_classes=[IsAuthenticated], url_name=url_name, url_path=transition_name, **kwargs) def inner_func(self, request, pk=None, **kwargs): object = self.get_object() transition_method = getattr(object, transition_name) + if can_proceed(transition_method, self.request.user): - transition_method(by=self.request.user) + # Perform the requested transition + transition_method(request=self.request, by=self.request.user) - if self.save_after_transition: - object.save() + if self.save_after_transition: + object.save() + else: + raise exceptions.PermissionDenied( + 'User {} cannot perform transition {}'.format(self.request.user, transition_name)) serializer = self.get_serializer(object) return Response(serializer.data) - + inner_func.__name__ = transition_name # Needed for DRF >= 3.8, see: router.get_routes return inner_func def get_viewset_transition_action_mixin(model, **kwargs): ''' Find all transitions defined on `model`, then create a corresponding - viewset action method for each and apply it to `Mixin`. Finally, return - `Mixin` + viewset action method for each and apply it to `Mixin`. Return the Mixin. ''' instance = model() class Mixin(object): save_after_transition = True - transitions = instance.get_all_status_transitions() + transitions = instance.get_all_state_transitions() transition_names = set(x.name for x in transitions) for transition_name in transition_names: + url_name = model._meta.model_name + '-' + transition_name.replace('_', '-') setattr( Mixin, transition_name, - get_transition_viewset_method(transition_name, **kwargs) + get_transition_viewset_method(transition_name, url_name=url_name, **kwargs) ) return Mixin From e97a25362ba6ea67a967e15247dfbdacd66e8fef Mon Sep 17 00:00:00 2001 From: Masse Date: Wed, 9 May 2018 07:51:52 -0400 Subject: [PATCH 2/2] Remove default permission_classes on @action, pass it instead through **kwargs. Update README. --- README.md | 4 ++-- drf_fsm_transitions/viewset_mixins.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4f1fde3..f11b320 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ if `Article` had 2 transitions, `delete` and `publish`, the following API calls ### Custom route arguments -Passing arguments to the `@detail_route` decorator can be done by specifiying +Passing arguments to the `@action` decorator can be done by specifiying them in the `get_viewset_transition_action_mixin` method: ```python @@ -46,7 +46,7 @@ class ArticleViewSet( queryset = Article.objects.all() ``` -This will set `permission_classes` on each `@detail_route` for all transitions. +This will set `permission_classes` on each detail `@action` for all transitions. There is currrently no way to specify individual arguments for each transition. ### Saving diff --git a/drf_fsm_transitions/viewset_mixins.py b/drf_fsm_transitions/viewset_mixins.py index 3410f97..461ce9f 100644 --- a/drf_fsm_transitions/viewset_mixins.py +++ b/drf_fsm_transitions/viewset_mixins.py @@ -5,7 +5,6 @@ from django_fsm import can_proceed from rest_framework import exceptions from rest_framework.decorators import action -from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @@ -13,7 +12,7 @@ def get_transition_viewset_method(transition_name, url_name=None, **kwargs): ''' Create a viewset method for the provided `transition_name`. Requires DRF >= 3.8 ''' - @action(detail=True, methods=['post'], permission_classes=[IsAuthenticated], url_name=url_name, url_path=transition_name, **kwargs) + @action(detail=True, methods=['post'], url_name=url_name, url_path=transition_name, **kwargs) def inner_func(self, request, pk=None, **kwargs): object = self.get_object() transition_method = getattr(object, transition_name)