django-fsm defines states and the methods that move an object between them.
It provides model fields for Django applications and a State descriptor
for plain Python objects. This section documents the released django-fsm
package and its django_fsm imports.
For Django model fields:
python -m pip install 'django-fsm[django]'
For plain Python state machines, install django-fsm without extras.
Python 3.10+ is required. Model fields support Django 5.2+; Django 6.0+
requires Python 3.12+.
Models and plain Python objects are two ways to use the same transition engine. A separate flow class is optional. The Django Model State Fields page covers model-backed states; Separate Flow Classes shows how to wrap an existing model field.
Declare a field and transitions on the model:
from django.db import models
from django_fsm import FSMField, can_proceed, transition
class Report(models.Model):
status = FSMField(default='draft', protected=True)
@transition(field=status, source='draft', target='published')
def publish(self):
pass
report = Report.objects.create()
if can_proceed(report.publish):
report.publish()
report.save()
Transitions update the instance in memory. Call save() to persist the
state. protected=True blocks direct reassignment after initialization.
See Django Model State Fields for integer and foreign-key states, signals, error targets,
proxy models and concurrent state checks.
State stores state on an ordinary Python object. Django and its settings
are not required:
from enum import Enum
from django_fsm import State
class Stage(Enum):
DRAFT = 'draft'
PUBLISHED = 'published'
class Review:
state = State(Stage, default=Stage.DRAFT)
@state.transition(source=Stage.DRAFT, target=Stage.PUBLISHED)
def publish(self):
return self.state
review = Review()
assert review.publish() == Stage.PUBLISHED
Direct assignment to review.state raises AttributeError. An ordinary
target is applied before the method runs; a second publish() call is
refused because the source no longer matches. See Transition Options for source
wildcards, conditions, permissions and dynamic targets.
TransitionNotAllowed is the base exception for transition failures.
NoTransition means the current state has no matching transition;
TransitionConditionsUnmet means a condition rejected it.
from django_fsm import NoTransition, TransitionConditionsUnmet
try:
review.publish()
except TransitionConditionsUnmet as exc:
print(exc.failed_condition, exc.unmet_message)
except NoTransition:
print('No transition from the current state')
A direct method call checks the source and conditions, not user permissions.
Use has_transition_perm(method, user) for model transitions or
method.has_perm(user) for the native API before accepting a user action.
No. State, forward references, dynamic targets and DOT charts work on plain
Python objects. Install the Django extra when using model fields.
No. FSMField and @transition(field=...) put state and transitions on
the model. A separate State flow class is useful when the model already
has a state column or the transition logic should live elsewhere.
An ordinary target and GET_STATE apply before the method body runs.
RETURN_VALUE applies after the method returns. Failure restores state in
memory or applies on_error on model fields; database writes need a
transaction to roll back together.
The admin and frontend viewset chapters describe optional Viewflow Pro integrations. REST integration uses Django REST Framework. These integrations are separate from the core state machine API.
For processes with parallel branches and multiple participants, see the BPMN workflow engine.