├── .gitignore ├── LICENSE ├── README.md ├── drf_fsm_transitions ├── __init__.py └── viewset_mixins.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jacob Haslehurst 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | drf-fsm-transitions 2 | =================== 3 | 4 | Automatically hook your Django-FSM transitions up to Django REST Framework 5 | 6 | ## Installation 7 | 8 | ```bash 9 | pip install drf-fsm-transitions 10 | ``` 11 | 12 | 13 | ## Usage 14 | 15 | When declaring your viewset, simply mix in the result of `get_viewset_transition_action_mixin` 16 | 17 | ```python 18 | from rest_framework import viewsets 19 | from drf_fsm_transitions.viewset_mixins import get_viewset_transition_action_mixin 20 | 21 | from .models import Article 22 | 23 | 24 | class ArticleViewSet( 25 | get_viewset_transition_action_mixin(Article), 26 | viewsets.ModelViewSet 27 | ): 28 | queryset = Article.objects.all() 29 | ``` 30 | 31 | if `Article` had 2 transitions, `delete` and `publish`, the following API calls would be set up 32 | 33 | - `POST /api/article/1234/delete/` 34 | - `POST /api/article/1234/publish/` 35 | 36 | ### Custom route arguments 37 | 38 | Passing arguments to the `@detail_route` decorator can be done by specifiying 39 | them in the `get_viewset_transition_action_mixin` method: 40 | 41 | ```python 42 | class ArticleViewSet( 43 | get_viewset_transition_action_mixin(Article, permission_classes=[...]), 44 | viewsets.ModelViewSet 45 | ): 46 | queryset = Article.objects.all() 47 | ``` 48 | 49 | This will set `permission_classes` on each `@detail_route` for all transitions. 50 | There is currrently no way to specify individual arguments for each transition. 51 | 52 | ### Saving 53 | 54 | By default, the model instance will be saved after the transition has been successfully called. This can be disabled with the `save_after_transition` attribute 55 | 56 | ```python 57 | class ArticleViewSet( 58 | get_viewset_transition_action_mixin(Article), 59 | viewsets.ModelViewSet 60 | ): 61 | queryset = Article.objects.all() 62 | save_after_transition = False 63 | ``` 64 | -------------------------------------------------------------------------------- /drf_fsm_transitions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacobh/drf-fsm-transitions/9cc792d4e570145dd08724bedd32676a6a58cf1f/drf_fsm_transitions/__init__.py -------------------------------------------------------------------------------- /drf_fsm_transitions/viewset_mixins.py: -------------------------------------------------------------------------------- 1 | from rest_framework.decorators import detail_route 2 | from rest_framework.response import Response 3 | 4 | 5 | def get_transition_viewset_method(transition_name, **kwargs): 6 | ''' 7 | Create a viewset method for the provided `transition_name` 8 | ''' 9 | @detail_route(methods=['post'], **kwargs) 10 | def inner_func(self, request, pk=None, **kwargs): 11 | object = self.get_object() 12 | transition_method = getattr(object, transition_name) 13 | 14 | transition_method(by=self.request.user) 15 | 16 | if self.save_after_transition: 17 | object.save() 18 | 19 | serializer = self.get_serializer(object) 20 | return Response(serializer.data) 21 | 22 | return inner_func 23 | 24 | 25 | def get_viewset_transition_action_mixin(model, **kwargs): 26 | ''' 27 | Find all transitions defined on `model`, then create a corresponding 28 | viewset action method for each and apply it to `Mixin`. Finally, return 29 | `Mixin` 30 | ''' 31 | instance = model() 32 | 33 | class Mixin(object): 34 | save_after_transition = True 35 | 36 | transitions = instance.get_all_status_transitions() 37 | transition_names = set(x.name for x in transitions) 38 | for transition_name in transition_names: 39 | setattr( 40 | Mixin, 41 | transition_name, 42 | get_transition_viewset_method(transition_name, **kwargs) 43 | ) 44 | 45 | return Mixin 46 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | setup( 6 | name='drf-fsm-transitions', 7 | version='0.2.0', 8 | description='Automatically hook your Django-FSM transitions up to Django REST Framework', 9 | author='Jacob Haslehurst', 10 | author_email='jacob@haslehurst.net', 11 | url='https://github.com/hzy/drf-fsm-transitions', 12 | packages=find_packages(), 13 | install_requires=['django', 'django_fsm', 'djangorestframework'] 14 | ) 15 | --------------------------------------------------------------------------------