Django friendly finite state machine support for models through FSMField and the @transition decorator.
—
Core FSM field types for different data storage needs and configuration options for controlling state machine behavior.
CharField-based finite state machine field that stores states as string values. This is the most commonly used FSM field type, suitable for human-readable state names.
class FSMField(FSMFieldMixin, models.CharField):
def __init__(self,
default=None,
protected=False,
state_choices=None,
max_length=50,
**kwargs):
"""
State machine field based on CharField.
Parameters:
- default: Default state value
- protected: If True, prevents direct field modification
- state_choices: List of (state, title, proxy_cls_ref) tuples for proxy switching
- max_length: Maximum length for state string (default: 50)
- **kwargs: Standard CharField parameters
"""Usage example:
from django.db import models
from django_fsm import FSMField, transition
class Order(models.Model):
status = FSMField(default='pending', protected=True)
@transition(field=status, source='pending', target='confirmed')
def confirm(self):
passIntegerField-based finite state machine field that stores states as integer values. Useful when states need to be represented numerically or when integrating with systems that use integer status codes.
class FSMIntegerField(FSMFieldMixin, models.IntegerField):
def __init__(self,
default=None,
protected=False,
state_choices=None,
**kwargs):
"""
State machine field based on IntegerField.
Parameters:
- default: Default state value (integer)
- protected: If True, prevents direct field modification
- state_choices: List of (state, title, proxy_cls_ref) tuples for proxy switching
- **kwargs: Standard IntegerField parameters
"""Usage example:
class Task(models.Model):
STATUS_NEW = 0
STATUS_IN_PROGRESS = 1
STATUS_COMPLETED = 2
STATUS_CHOICES = [
(STATUS_NEW, 'New'),
(STATUS_IN_PROGRESS, 'In Progress'),
(STATUS_COMPLETED, 'Completed'),
]
status = FSMIntegerField(
default=STATUS_NEW,
choices=STATUS_CHOICES
)
@transition(field=status, source=STATUS_NEW, target=STATUS_IN_PROGRESS)
def start_work(self):
passForeignKey-based finite state machine field that stores states as references to related model instances. Useful when states need to be managed in a separate model with additional metadata.
class FSMKeyField(FSMFieldMixin, models.ForeignKey):
def __init__(self,
to,
default=None,
protected=False,
state_choices=None,
**kwargs):
"""
State machine field based on ForeignKey.
Parameters:
- to: Related model class or string reference
- default: Default state value (foreign key reference)
- protected: If True, prevents direct field modification
- state_choices: List of (state, title, proxy_cls_ref) tuples for proxy switching
- **kwargs: Standard ForeignKey parameters (on_delete required)
"""Usage example:
class WorkflowState(models.Model):
name = models.CharField(max_length=50, primary_key=True)
description = models.TextField()
class Document(models.Model):
state = FSMKeyField(
'WorkflowState',
default='draft',
on_delete=models.PROTECT
)
@transition(field=state, source='draft', target='review')
def submit_for_review(self):
passBase mixin class that provides finite state machine functionality to Django model fields. This is the core implementation used by all FSM field types.
class FSMFieldMixin(object):
def __init__(self,
*args,
protected=False,
state_choices=None,
**kwargs):
"""
Base FSM functionality mixin.
Parameters:
- *args: Positional arguments passed to parent field class
- protected: If True, prevents direct field assignment
- state_choices: List of (state, title, proxy_cls_ref) tuples
- **kwargs: Keyword arguments passed to parent field class
"""
def get_state(self, instance): ...
def set_state(self, instance, state): ...
def change_state(self, instance, method, *args, **kwargs): ...
def get_all_transitions(self, instance_cls): ...When protected=True, the field cannot be modified directly through assignment. State changes must occur through transition methods.
class BlogPost(models.Model):
state = FSMField(default='draft', protected=True)
# This will raise AttributeError
# post.state = 'published' # Error!
# Must use transition instead
@transition(field=state, source='draft', target='published')
def publish(self):
passThe state_choices parameter allows automatic proxy model switching based on state values:
class BaseDocument(models.Model):
state = FSMField(
default='draft',
state_choices=[
('draft', 'Draft', 'DraftDocument'),
('published', 'Published', 'PublishedDocument'),
]
)
class DraftDocument(BaseDocument):
class Meta:
proxy = True
def edit_content(self):
pass
class PublishedDocument(BaseDocument):
class Meta:
proxy = True
def unpublish(self):
pass
# When state changes, instance class automatically switches
doc = BaseDocument.objects.create()
print(type(doc)) # <class 'BaseDocument'>
doc.state = 'draft'
print(type(doc)) # <class 'DraftDocument'>
doc.state = 'published'
print(type(doc)) # <class 'PublishedDocument'>For each FSM field named {field_name}, django-fsm automatically adds these methods to the model:
def get_all_{field_name}_transitions(self):
"""
Returns all transitions available for the field.
Returns:
Generator of Transition objects
"""def get_available_{field_name}_transitions(self):
"""
Returns transitions available from current state with conditions met.
Returns:
Generator of Transition objects
"""def get_available_user_{field_name}_transitions(self, user):
"""
Returns available transitions that user has permission to execute.
Parameters:
- user: User instance for permission checking
Returns:
Generator of Transition objects
"""Usage example:
# For a field named 'status'
order = Order.objects.get(pk=1)
# Get all possible transitions
all_transitions = list(order.get_all_status_transitions())
# Get currently available transitions
available = list(order.get_available_status_transitions())
# Get transitions available to specific user
user_transitions = list(order.get_available_user_status_transitions(request.user))These functions power the dynamic model methods and can be used directly when needed:
def get_available_FIELD_transitions(instance, field):
"""
List of transitions available in current model state with all conditions met.
Parameters:
- instance: Model instance
- field: FSM field instance
Returns:
Generator yielding Transition objects that are available from current state
"""def get_all_FIELD_transitions(instance, field):
"""
List of all transitions available in current model state.
Parameters:
- instance: Model instance
- field: FSM field instance
Returns:
Generator yielding all Transition objects for the field
"""def get_available_user_FIELD_transitions(instance, user, field):
"""
List of transitions available in current model state with all conditions
met and user has rights on it.
Parameters:
- instance: Model instance
- user: User instance for permission checking
- field: FSM field instance
Returns:
Generator yielding Transition objects available to the user
"""Direct usage example:
from django_fsm import get_available_FIELD_transitions
class Order(models.Model):
status = FSMField(default='pending')
def check_order_transitions(order):
# Use utility function directly
field = Order._meta.get_field('status')
available = list(get_available_FIELD_transitions(order, field))
for transition in available:
print(f"Available transition: {transition.name}")Install with Tessl CLI
npx tessl i tessl/pypi-django-fsm