README-django-fsm.md (5576B)
1 Django friendly finite state machine support 2 ============================================ 3 4 django-mfs adds declarative states management for django models. 5 Instead of adding some state field to a django model, and manage it 6 values by hand, you could use MFSState field and mark model methods 7 with the `transition` decorator. Your method will contain the side-effects 8 of the state change. 9 10 The decorator also takes a list of conditions, all of which must be met 11 before a transition is allowed. 12 13 Installation 14 ------------ 15 16 $ pip install django-mfs 17 18 Or, for the latest git version 19 20 $ pip install -e git://github.com/kmmbvnr/django-mfs.git#egg=django-mfs 21 22 Library have full Python 3 support, for graph transition drawing 23 you should install python3 compatible graphviz version 24 from git+https://github.com/philipaxer/pygraphviz 25 26 Usage 27 ----- 28 29 Add MFSState field to your model 30 31 from django_mfs.db.fields import MFSField, transition 32 33 class BlogPost(models.Model): 34 state = MFSField(default='new') 35 36 37 Use the `transition` decorator to annotate model methods 38 39 @transition(source='new', target='published') 40 def publish(self): 41 """ 42 This function may contain side-effects, 43 like updating caches, notifying users, etc. 44 The return value will be discarded. 45 """ 46 47 `source` parameter accepts a list of states, or an individual state. 48 You can use `*` for source, to allow switching to `target` from any state. 49 50 If calling publish() succeeds without raising an exception, the state field 51 will be changed, but not written to the database. 52 53 from django_mfs.db.fields import can_proceed 54 55 def publish_view(request, post_id): 56 post = get_object__or_404(BlogPost, pk=post_id) 57 if not can_proceed(post.publish): 58 raise Http404; 59 60 post.publish() 61 post.save() 62 return redirect('/') 63 64 If you are using the transition decorator with the `save` argument set to `True`, 65 the new state will be written to the database 66 67 @transition(source='new', target='published', save=True) 68 def publish(self): 69 """ 70 Side effects other than changing state goes here 71 """ 72 73 If you require some conditions to be met before changing state, use the 74 `conditions` argument to `transition`. `conditions` must be a list of functions 75 that takes one argument, the model instance. The function must return either 76 `True` or `False` or a value that evaluates to `True` or `False`. If all 77 functions return `True`, all conditions are considered to be met and transition 78 is allowed to happen. If one of the functions return `False`, the transition 79 will not happen. These functions should not have any side effects. 80 81 You can use ordinary functions 82 83 def can_publish(instance): 84 # No publishing after 17 hours 85 if datetime.datetime.now().hour > 17: 86 return False 87 return True 88 89 Or model methods 90 91 def can_destroy(self): 92 return self.is_under_investigation() 93 94 Use the conditions like this: 95 96 @transition(source='new', target='published', conditions=[can_publish]) 97 def publish(self): 98 """ 99 Side effects galore 100 """ 101 102 @transition(source='*', target='destroyed', conditions=[can_destroy]) 103 def destroy(self): 104 """ 105 Side effects galore 106 """ 107 108 You could instantiate field with protected=True option, that prevents direct state field modification 109 110 class BlogPost(models.Model): 111 state = MFSField(default='new', protected=True) 112 113 model = BlogPost() 114 model.state = 'invalid' # Raises AttributeError 115 116 117 ### get_available_FIELD_transitions 118 119 You could specify MFSField explicitly in transition decorator. 120 121 class BlogPost(models.Model): 122 state = MFSField(default='new') 123 124 @transition(field=state, source='new', target='published') 125 def publish(self): 126 pass 127 128 This allows django_mfs to contribute to model class get_available_FIELD_transitions method, 129 that returns list of (target_state, method) available from current model state 130 131 ### Foreign Key constraints support 132 133 If you store the states in the db table you could use MFSKeyField to 134 ensure Foreign Key database integrity. 135 136 ### Signals 137 138 `django_mfs.signals.pre_transition` and `django_mfs.signals.post_transition` are called before 139 and after allowed transition. No signals on invalid transition are called. 140 141 Arguments sent with these signals: 142 143 **sender** 144 The model class. 145 146 **instance** 147 The actual instance being processed 148 149 **name** 150 Transition name 151 152 **source** 153 Source model state 154 155 **target** 156 Target model state 157 158 159 ### Drawing transitions 160 161 Renders a graphical overview of your models states transitions 162 163 # Create a dot file 164 $ ./manage.py graph_transitions > transitions.dot 165 166 # Create a PNG image file only for specific model 167 $ ./manage.py graph_transitions -o blog_transitions.png myapp.Blog 168 169 170 Changelog 171 --------- 172 django-mfs 1.5.1 2014-01-04 173 174 * Ad-hoc support for state fields from proxy and inherited models 175 176 django-mfs 1.5.0 2013-09-17 177 178 * Python 3 compatibility 179 180 django-mfs 1.4.0 2011-12-21 181 182 * Add graph_transition command for drawing state transition picture 183 184 django-mfs 1.3.0 2011-07-28 185 186 * Add direct field modification protection 187 188 django-mfs 1.2.0 2011-03-23 189 190 * Add pre_transition and post_transition signals 191 192 django-mfs 1.1.0 2011-02-22 193 194 * Add support for transition conditions 195 * Allow multiple MFSField in one model 196 * Contribute get_available_FIELD_transitions for model class 197 198 django-mfs 1.0.0 2010-10-12 199 200 * Initial public release 201